@code-pushup/core 0.48.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +886 -1081
- package/package.json +5 -5
- package/src/lib/compare.d.ts +2 -2
package/index.js
CHANGED
|
@@ -743,11 +743,262 @@ function comparePairs(pairs, equalsFn) {
|
|
|
743
743
|
import { spawn } from "node:child_process";
|
|
744
744
|
|
|
745
745
|
// packages/utils/src/lib/reports/utils.ts
|
|
746
|
-
import
|
|
746
|
+
import ansis from "ansis";
|
|
747
|
+
import { md } from "build-md";
|
|
748
|
+
|
|
749
|
+
// packages/utils/src/lib/reports/constants.ts
|
|
750
|
+
var TERMINAL_WIDTH = 80;
|
|
751
|
+
var SCORE_COLOR_RANGE = {
|
|
752
|
+
GREEN_MIN: 0.9,
|
|
753
|
+
YELLOW_MIN: 0.5
|
|
754
|
+
};
|
|
755
|
+
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
756
|
+
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
757
|
+
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
758
|
+
var REPORT_HEADLINE_TEXT = "Code PushUp Report";
|
|
759
|
+
var REPORT_RAW_OVERVIEW_TABLE_HEADERS = [
|
|
760
|
+
"Category",
|
|
761
|
+
"Score",
|
|
762
|
+
"Audits"
|
|
763
|
+
];
|
|
764
|
+
|
|
765
|
+
// packages/utils/src/lib/reports/utils.ts
|
|
766
|
+
function formatReportScore(score) {
|
|
767
|
+
const scaledScore = score * 100;
|
|
768
|
+
const roundedScore = Math.round(scaledScore);
|
|
769
|
+
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
770
|
+
}
|
|
771
|
+
function formatScoreWithColor(score, options) {
|
|
772
|
+
const styledNumber = options?.skipBold ? formatReportScore(score) : md.bold(formatReportScore(score));
|
|
773
|
+
return md`${scoreMarker(score)} ${styledNumber}`;
|
|
774
|
+
}
|
|
775
|
+
var MARKERS = {
|
|
776
|
+
circle: {
|
|
777
|
+
red: "\u{1F534}",
|
|
778
|
+
yellow: "\u{1F7E1}",
|
|
779
|
+
green: "\u{1F7E2}"
|
|
780
|
+
},
|
|
781
|
+
square: {
|
|
782
|
+
red: "\u{1F7E5}",
|
|
783
|
+
yellow: "\u{1F7E8}",
|
|
784
|
+
green: "\u{1F7E9}"
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
function scoreMarker(score, markerType = "circle") {
|
|
788
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
789
|
+
return MARKERS[markerType].green;
|
|
790
|
+
}
|
|
791
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
792
|
+
return MARKERS[markerType].yellow;
|
|
793
|
+
}
|
|
794
|
+
return MARKERS[markerType].red;
|
|
795
|
+
}
|
|
796
|
+
function getDiffMarker(diff) {
|
|
797
|
+
if (diff > 0) {
|
|
798
|
+
return "\u2191";
|
|
799
|
+
}
|
|
800
|
+
if (diff < 0) {
|
|
801
|
+
return "\u2193";
|
|
802
|
+
}
|
|
803
|
+
return "";
|
|
804
|
+
}
|
|
805
|
+
function colorByScoreDiff(text, diff) {
|
|
806
|
+
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
807
|
+
return shieldsBadge(text, color);
|
|
808
|
+
}
|
|
809
|
+
function shieldsBadge(text, color) {
|
|
810
|
+
return md.image(
|
|
811
|
+
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
812
|
+
text
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
function formatDiffNumber(diff) {
|
|
816
|
+
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
817
|
+
const sign = diff < 0 ? "\u2212" : "+";
|
|
818
|
+
return `${sign}${number}`;
|
|
819
|
+
}
|
|
820
|
+
function severityMarker(severity) {
|
|
821
|
+
if (severity === "error") {
|
|
822
|
+
return "\u{1F6A8}";
|
|
823
|
+
}
|
|
824
|
+
if (severity === "warning") {
|
|
825
|
+
return "\u26A0\uFE0F";
|
|
826
|
+
}
|
|
827
|
+
return "\u2139\uFE0F";
|
|
828
|
+
}
|
|
829
|
+
function formatScoreChange(diff) {
|
|
830
|
+
const marker = getDiffMarker(diff);
|
|
831
|
+
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
832
|
+
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
833
|
+
}
|
|
834
|
+
function formatValueChange({
|
|
835
|
+
values,
|
|
836
|
+
scores
|
|
837
|
+
}) {
|
|
838
|
+
const marker = getDiffMarker(values.diff);
|
|
839
|
+
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
840
|
+
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
841
|
+
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
842
|
+
}
|
|
843
|
+
function calcDuration(start, stop) {
|
|
844
|
+
return Math.round((stop ?? performance.now()) - start);
|
|
845
|
+
}
|
|
846
|
+
function countCategoryAudits(refs, plugins) {
|
|
847
|
+
const groupLookup = plugins.reduce(
|
|
848
|
+
(lookup, plugin) => {
|
|
849
|
+
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
850
|
+
return lookup;
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
...lookup,
|
|
854
|
+
[plugin.slug]: Object.fromEntries(
|
|
855
|
+
plugin.groups.map((group) => [group.slug, group])
|
|
856
|
+
)
|
|
857
|
+
};
|
|
858
|
+
},
|
|
859
|
+
{}
|
|
860
|
+
);
|
|
861
|
+
return refs.reduce((acc, ref) => {
|
|
862
|
+
if (ref.type === "group") {
|
|
863
|
+
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
864
|
+
return acc + (groupRefs?.length ?? 0);
|
|
865
|
+
}
|
|
866
|
+
return acc + 1;
|
|
867
|
+
}, 0);
|
|
868
|
+
}
|
|
869
|
+
function compareCategoryAuditsAndGroups(a, b) {
|
|
870
|
+
if (a.weight !== b.weight) {
|
|
871
|
+
return b.weight - a.weight;
|
|
872
|
+
}
|
|
873
|
+
if (a.score !== b.score) {
|
|
874
|
+
return a.score - b.score;
|
|
875
|
+
}
|
|
876
|
+
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
877
|
+
return b.value - a.value;
|
|
878
|
+
}
|
|
879
|
+
return a.title.localeCompare(b.title);
|
|
880
|
+
}
|
|
881
|
+
function compareAudits(a, b) {
|
|
882
|
+
if (a.score !== b.score) {
|
|
883
|
+
return a.score - b.score;
|
|
884
|
+
}
|
|
885
|
+
if (a.value !== b.value) {
|
|
886
|
+
return b.value - a.value;
|
|
887
|
+
}
|
|
888
|
+
return a.title.localeCompare(b.title);
|
|
889
|
+
}
|
|
890
|
+
function compareIssueSeverity(severity1, severity2) {
|
|
891
|
+
const levels = {
|
|
892
|
+
info: 0,
|
|
893
|
+
warning: 1,
|
|
894
|
+
error: 2
|
|
895
|
+
};
|
|
896
|
+
return levels[severity1] - levels[severity2];
|
|
897
|
+
}
|
|
898
|
+
function throwIsNotPresentError(itemName, presentPlace) {
|
|
899
|
+
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
900
|
+
}
|
|
901
|
+
function getPluginNameFromSlug(slug, plugins) {
|
|
902
|
+
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
903
|
+
}
|
|
904
|
+
function compareIssues(a, b) {
|
|
905
|
+
if (a.severity !== b.severity) {
|
|
906
|
+
return -compareIssueSeverity(a.severity, b.severity);
|
|
907
|
+
}
|
|
908
|
+
if (!a.source && b.source) {
|
|
909
|
+
return -1;
|
|
910
|
+
}
|
|
911
|
+
if (a.source && !b.source) {
|
|
912
|
+
return 1;
|
|
913
|
+
}
|
|
914
|
+
if (a.source?.file !== b.source?.file) {
|
|
915
|
+
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
916
|
+
}
|
|
917
|
+
if (!a.source?.position && b.source?.position) {
|
|
918
|
+
return -1;
|
|
919
|
+
}
|
|
920
|
+
if (a.source?.position && !b.source?.position) {
|
|
921
|
+
return 1;
|
|
922
|
+
}
|
|
923
|
+
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
924
|
+
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
925
|
+
}
|
|
926
|
+
return 0;
|
|
927
|
+
}
|
|
928
|
+
function applyScoreColor({ score, text }, style = ansis) {
|
|
929
|
+
const formattedScore = text ?? formatReportScore(score);
|
|
930
|
+
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
931
|
+
return text ? style.green(formattedScore) : style.bold(style.green(formattedScore));
|
|
932
|
+
}
|
|
933
|
+
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
934
|
+
return text ? style.yellow(formattedScore) : style.bold(style.yellow(formattedScore));
|
|
935
|
+
}
|
|
936
|
+
return text ? style.red(formattedScore) : style.bold(style.red(formattedScore));
|
|
937
|
+
}
|
|
938
|
+
function targetScoreIcon(score, targetScore, options = {}) {
|
|
939
|
+
if (targetScore != null) {
|
|
940
|
+
const {
|
|
941
|
+
passIcon = "\u2705",
|
|
942
|
+
failIcon = "\u274C",
|
|
943
|
+
prefix = "",
|
|
944
|
+
postfix = ""
|
|
945
|
+
} = options;
|
|
946
|
+
if (score >= targetScore) {
|
|
947
|
+
return `${prefix}${passIcon}${postfix}`;
|
|
948
|
+
}
|
|
949
|
+
return `${prefix}${failIcon}${postfix}`;
|
|
950
|
+
}
|
|
951
|
+
return "";
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// packages/utils/src/lib/execute-process.ts
|
|
955
|
+
var ProcessError = class extends Error {
|
|
956
|
+
code;
|
|
957
|
+
stderr;
|
|
958
|
+
stdout;
|
|
959
|
+
constructor(result) {
|
|
960
|
+
super(result.stderr);
|
|
961
|
+
this.code = result.code;
|
|
962
|
+
this.stderr = result.stderr;
|
|
963
|
+
this.stdout = result.stdout;
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
function executeProcess(cfg) {
|
|
967
|
+
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
|
|
968
|
+
const { onStdout, onError, onComplete } = observer ?? {};
|
|
969
|
+
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
970
|
+
const start = performance.now();
|
|
971
|
+
return new Promise((resolve, reject) => {
|
|
972
|
+
const process2 = spawn(command, args, { cwd, shell: true });
|
|
973
|
+
let stdout = "";
|
|
974
|
+
let stderr = "";
|
|
975
|
+
process2.stdout.on("data", (data) => {
|
|
976
|
+
stdout += String(data);
|
|
977
|
+
onStdout?.(String(data));
|
|
978
|
+
});
|
|
979
|
+
process2.stderr.on("data", (data) => {
|
|
980
|
+
stderr += String(data);
|
|
981
|
+
});
|
|
982
|
+
process2.on("error", (err) => {
|
|
983
|
+
stderr += err.toString();
|
|
984
|
+
});
|
|
985
|
+
process2.on("close", (code2) => {
|
|
986
|
+
const timings = { date, duration: calcDuration(start) };
|
|
987
|
+
if (code2 === 0 || ignoreExitCode) {
|
|
988
|
+
onComplete?.();
|
|
989
|
+
resolve({ code: code2, stdout, stderr, ...timings });
|
|
990
|
+
} else {
|
|
991
|
+
const errorMsg = new ProcessError({ code: code2, stdout, stderr, ...timings });
|
|
992
|
+
onError?.(errorMsg);
|
|
993
|
+
reject(errorMsg);
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
});
|
|
997
|
+
}
|
|
747
998
|
|
|
748
999
|
// packages/utils/src/lib/file-system.ts
|
|
1000
|
+
import { bold, gray } from "ansis";
|
|
749
1001
|
import { bundleRequire } from "bundle-require";
|
|
750
|
-
import chalk2 from "chalk";
|
|
751
1002
|
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
752
1003
|
|
|
753
1004
|
// packages/utils/src/lib/formatting.ts
|
|
@@ -810,55 +1061,7 @@ function isPromiseRejectedResult(result) {
|
|
|
810
1061
|
// packages/utils/src/lib/logging.ts
|
|
811
1062
|
import isaacs_cliui from "@isaacs/cliui";
|
|
812
1063
|
import { cliui } from "@poppinss/cliui";
|
|
813
|
-
import
|
|
814
|
-
|
|
815
|
-
// packages/utils/src/lib/reports/constants.ts
|
|
816
|
-
var TERMINAL_WIDTH = 80;
|
|
817
|
-
var SCORE_COLOR_RANGE = {
|
|
818
|
-
GREEN_MIN: 0.9,
|
|
819
|
-
YELLOW_MIN: 0.5
|
|
820
|
-
};
|
|
821
|
-
var CATEGORIES_TITLE = "\u{1F3F7} Categories";
|
|
822
|
-
var FOOTER_PREFIX = "Made with \u2764 by";
|
|
823
|
-
var CODE_PUSHUP_DOMAIN = "code-pushup.dev";
|
|
824
|
-
var README_LINK = "https://github.com/code-pushup/cli#readme";
|
|
825
|
-
var reportHeadlineText = "Code PushUp Report";
|
|
826
|
-
var reportOverviewTableHeaders = [
|
|
827
|
-
{
|
|
828
|
-
key: "category",
|
|
829
|
-
label: "\u{1F3F7} Category",
|
|
830
|
-
align: "left"
|
|
831
|
-
},
|
|
832
|
-
{
|
|
833
|
-
key: "score",
|
|
834
|
-
label: "\u2B50 Score"
|
|
835
|
-
},
|
|
836
|
-
{
|
|
837
|
-
key: "audits",
|
|
838
|
-
label: "\u{1F6E1} Audits"
|
|
839
|
-
}
|
|
840
|
-
];
|
|
841
|
-
var reportRawOverviewTableHeaders = ["Category", "Score", "Audits"];
|
|
842
|
-
var issuesTableHeadings = [
|
|
843
|
-
{
|
|
844
|
-
key: "severity",
|
|
845
|
-
label: "Severity"
|
|
846
|
-
},
|
|
847
|
-
{
|
|
848
|
-
key: "message",
|
|
849
|
-
label: "Message"
|
|
850
|
-
},
|
|
851
|
-
{
|
|
852
|
-
key: "file",
|
|
853
|
-
label: "Source file"
|
|
854
|
-
},
|
|
855
|
-
{
|
|
856
|
-
key: "line",
|
|
857
|
-
label: "Line(s)"
|
|
858
|
-
}
|
|
859
|
-
];
|
|
860
|
-
|
|
861
|
-
// packages/utils/src/lib/logging.ts
|
|
1064
|
+
import { underline } from "ansis";
|
|
862
1065
|
var singletonUiInstance;
|
|
863
1066
|
function ui() {
|
|
864
1067
|
if (singletonUiInstance === void 0) {
|
|
@@ -954,10 +1157,10 @@ async function ensureDirectoryExists(baseDir) {
|
|
|
954
1157
|
function logMultipleFileResults(fileResults, messagePrefix) {
|
|
955
1158
|
const succeededTransform = (result) => {
|
|
956
1159
|
const [fileName, size] = result.value;
|
|
957
|
-
const formattedSize = size ? ` (${
|
|
958
|
-
return `- ${
|
|
1160
|
+
const formattedSize = size ? ` (${gray(formatBytes(size))})` : "";
|
|
1161
|
+
return `- ${bold(fileName)}${formattedSize}`;
|
|
959
1162
|
};
|
|
960
|
-
const failedTransform = (result) => `- ${
|
|
1163
|
+
const failedTransform = (result) => `- ${bold(result.reason)}`;
|
|
961
1164
|
logMultipleResults(
|
|
962
1165
|
fileResults,
|
|
963
1166
|
messagePrefix,
|
|
@@ -973,570 +1176,32 @@ async function importModule(options) {
|
|
|
973
1176
|
return mod;
|
|
974
1177
|
}
|
|
975
1178
|
|
|
976
|
-
// packages/utils/src/lib/
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
var SPACE = " ";
|
|
1179
|
+
// packages/utils/src/lib/git/git.ts
|
|
1180
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
1181
|
+
import { simpleGit } from "simple-git";
|
|
980
1182
|
|
|
981
|
-
// packages/utils/src/lib/
|
|
982
|
-
function
|
|
983
|
-
return
|
|
984
|
-
NEW_LINE}${content}${NEW_LINE}${// @TODO in the future we could consider adding it only if the content ends with a code block
|
|
985
|
-
// ⚠️ The blank line ensure Markdown in content is rendered correctly.
|
|
986
|
-
NEW_LINE}</details>${// ⚠️ The blank line is needed to ensure Markdown after details is rendered correctly.
|
|
987
|
-
NEW_LINE}`;
|
|
1183
|
+
// packages/utils/src/lib/transform.ts
|
|
1184
|
+
function objectToEntries(obj) {
|
|
1185
|
+
return Object.entries(obj);
|
|
988
1186
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
var boldElement = "b";
|
|
992
|
-
function bold(text) {
|
|
993
|
-
return `<${boldElement}>${text}</${boldElement}>`;
|
|
994
|
-
}
|
|
995
|
-
var italicElement = "i";
|
|
996
|
-
function italic(text) {
|
|
997
|
-
return `<${italicElement}>${text}</${italicElement}>`;
|
|
998
|
-
}
|
|
999
|
-
var codeElement = "code";
|
|
1000
|
-
function code(text) {
|
|
1001
|
-
return `<${codeElement}>${text}</${codeElement}>`;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
// packages/utils/src/lib/text-formats/html/link.ts
|
|
1005
|
-
function link(href, text) {
|
|
1006
|
-
return `<a href="${href}">${text || href}</a>`;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// packages/utils/src/lib/transform.ts
|
|
1010
|
-
function objectToEntries(obj) {
|
|
1011
|
-
return Object.entries(obj);
|
|
1012
|
-
}
|
|
1013
|
-
function deepClone(obj) {
|
|
1014
|
-
return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
|
|
1015
|
-
}
|
|
1016
|
-
function toUnixPath(path) {
|
|
1017
|
-
return path.replace(/\\/g, "/");
|
|
1018
|
-
}
|
|
1019
|
-
function capitalize(text) {
|
|
1020
|
-
return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
|
|
1021
|
-
1
|
|
1022
|
-
)}`;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
// packages/utils/src/lib/text-formats/table.ts
|
|
1026
|
-
function rowToStringArray({ rows, columns = [] }) {
|
|
1027
|
-
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1028
|
-
throw new TypeError(
|
|
1029
|
-
"Column can`t be object when rows are primitive values"
|
|
1030
|
-
);
|
|
1031
|
-
}
|
|
1032
|
-
return rows.map((row) => {
|
|
1033
|
-
if (Array.isArray(row)) {
|
|
1034
|
-
return row.map(String);
|
|
1035
|
-
}
|
|
1036
|
-
const objectRow = row;
|
|
1037
|
-
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1038
|
-
return Object.values(objectRow).map(
|
|
1039
|
-
(value) => value == null ? "" : String(value)
|
|
1040
|
-
);
|
|
1041
|
-
}
|
|
1042
|
-
return columns.map(
|
|
1043
|
-
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1044
|
-
);
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
function columnsToStringArray({
|
|
1048
|
-
rows,
|
|
1049
|
-
columns = []
|
|
1050
|
-
}) {
|
|
1051
|
-
const firstRow = rows.at(0);
|
|
1052
|
-
const primitiveRows = Array.isArray(firstRow);
|
|
1053
|
-
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1054
|
-
throw new Error("invalid union type. Caught by model parsing.");
|
|
1055
|
-
}
|
|
1056
|
-
if (columns.length === 0) {
|
|
1057
|
-
if (Array.isArray(firstRow)) {
|
|
1058
|
-
return firstRow.map((_, idx) => String(idx));
|
|
1059
|
-
}
|
|
1060
|
-
return Object.keys(firstRow);
|
|
1061
|
-
}
|
|
1062
|
-
if (typeof columns.at(0) === "string") {
|
|
1063
|
-
return columns.map(String);
|
|
1064
|
-
}
|
|
1065
|
-
const cols = columns;
|
|
1066
|
-
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1067
|
-
}
|
|
1068
|
-
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1069
|
-
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1070
|
-
if (typeof column === "string") {
|
|
1071
|
-
return column;
|
|
1072
|
-
} else if (typeof column === "object") {
|
|
1073
|
-
return column.align ?? "center";
|
|
1074
|
-
} else {
|
|
1075
|
-
return "center";
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1079
|
-
const column = columns.at(targetIdx);
|
|
1080
|
-
if (column == null) {
|
|
1081
|
-
return "center";
|
|
1082
|
-
} else if (typeof column === "string") {
|
|
1083
|
-
return column;
|
|
1084
|
-
} else if (typeof column === "object") {
|
|
1085
|
-
return column.align ?? "center";
|
|
1086
|
-
} else {
|
|
1087
|
-
return "center";
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
function getColumnAlignments(tableData) {
|
|
1091
|
-
const { rows, columns = [] } = tableData;
|
|
1092
|
-
if (rows.at(0) == null) {
|
|
1093
|
-
throw new Error("first row can`t be undefined.");
|
|
1094
|
-
}
|
|
1095
|
-
if (Array.isArray(rows.at(0))) {
|
|
1096
|
-
const firstPrimitiveRow = rows.at(0);
|
|
1097
|
-
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1098
|
-
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1102
|
-
if (columns.length > 0) {
|
|
1103
|
-
return columns.map(
|
|
1104
|
-
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1105
|
-
column.key,
|
|
1106
|
-
idx,
|
|
1107
|
-
columns
|
|
1108
|
-
)
|
|
1109
|
-
);
|
|
1110
|
-
}
|
|
1111
|
-
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
// packages/utils/src/lib/text-formats/html/table.ts
|
|
1115
|
-
function wrap(elem, content) {
|
|
1116
|
-
return `<${elem}>${content}</${elem}>${NEW_LINE}`;
|
|
1117
|
-
}
|
|
1118
|
-
function wrapRow(content) {
|
|
1119
|
-
const elem = "tr";
|
|
1120
|
-
return `<${elem}>${NEW_LINE}${content}</${elem}>${NEW_LINE}`;
|
|
1121
|
-
}
|
|
1122
|
-
function table(tableData) {
|
|
1123
|
-
if (tableData.rows.length === 0) {
|
|
1124
|
-
throw new Error("Data can't be empty");
|
|
1125
|
-
}
|
|
1126
|
-
const tableHeaderCols = columnsToStringArray(tableData).map((s) => wrap("th", s)).join("");
|
|
1127
|
-
const tableHeaderRow = wrapRow(tableHeaderCols);
|
|
1128
|
-
const tableBody = rowToStringArray(tableData).map((arr) => {
|
|
1129
|
-
const columns = arr.map((s) => wrap("td", s)).join("");
|
|
1130
|
-
return wrapRow(columns);
|
|
1131
|
-
}).join("");
|
|
1132
|
-
return wrap("table", `${NEW_LINE}${tableHeaderRow}${tableBody}`);
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// packages/utils/src/lib/text-formats/md/font-style.ts
|
|
1136
|
-
var boldWrap = "**";
|
|
1137
|
-
function bold2(text) {
|
|
1138
|
-
return `${boldWrap}${text}${boldWrap}`;
|
|
1139
|
-
}
|
|
1140
|
-
var italicWrap = "_";
|
|
1141
|
-
function italic2(text) {
|
|
1142
|
-
return `${italicWrap}${text}${italicWrap}`;
|
|
1143
|
-
}
|
|
1144
|
-
var strikeThroughWrap = "~";
|
|
1145
|
-
function strikeThrough(text) {
|
|
1146
|
-
return `${strikeThroughWrap}${text}${strikeThroughWrap}`;
|
|
1147
|
-
}
|
|
1148
|
-
var codeWrap = "`";
|
|
1149
|
-
function code2(text) {
|
|
1150
|
-
return `${codeWrap}${text}${codeWrap}`;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
// packages/utils/src/lib/text-formats/md/headline.ts
|
|
1154
|
-
function headline(text, hierarchy = 1) {
|
|
1155
|
-
return `${"#".repeat(hierarchy)} ${text}${NEW_LINE}`;
|
|
1156
|
-
}
|
|
1157
|
-
function h(text, hierarchy = 1) {
|
|
1158
|
-
return headline(text, hierarchy);
|
|
1159
|
-
}
|
|
1160
|
-
function h1(text) {
|
|
1161
|
-
return headline(text, 1);
|
|
1162
|
-
}
|
|
1163
|
-
function h2(text) {
|
|
1164
|
-
return headline(text, 2);
|
|
1165
|
-
}
|
|
1166
|
-
function h3(text) {
|
|
1167
|
-
return headline(text, 3);
|
|
1168
|
-
}
|
|
1169
|
-
function h4(text) {
|
|
1170
|
-
return headline(text, 4);
|
|
1171
|
-
}
|
|
1172
|
-
function h5(text) {
|
|
1173
|
-
return headline(text, 5);
|
|
1174
|
-
}
|
|
1175
|
-
function h6(text) {
|
|
1176
|
-
return headline(text, 6);
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// packages/utils/src/lib/text-formats/md/image.ts
|
|
1180
|
-
function image(src, alt) {
|
|
1181
|
-
return ``;
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// packages/utils/src/lib/text-formats/md/link.ts
|
|
1185
|
-
function link2(href, text) {
|
|
1186
|
-
return `[${text || href}](${href})`;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
// packages/utils/src/lib/text-formats/md/list.ts
|
|
1190
|
-
function li(text, order = "unordered") {
|
|
1191
|
-
const style = order === "unordered" ? "-" : "- [ ]";
|
|
1192
|
-
return `${style} ${text}`;
|
|
1193
|
-
}
|
|
1194
|
-
function indentation(text, level = 1) {
|
|
1195
|
-
return `${TAB.repeat(level)}${text}`;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// packages/utils/src/lib/text-formats/md/paragraphs.ts
|
|
1199
|
-
function paragraphs(...sections) {
|
|
1200
|
-
return sections.filter(Boolean).join(`${NEW_LINE}${NEW_LINE}`);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
// packages/utils/src/lib/text-formats/md/section.ts
|
|
1204
|
-
function section(...contents) {
|
|
1205
|
-
return `${lines(...contents)}${NEW_LINE}`;
|
|
1206
|
-
}
|
|
1207
|
-
function lines(...contents) {
|
|
1208
|
-
const filteredContent = contents.filter(
|
|
1209
|
-
(value) => value != null && value !== "" && value !== false
|
|
1210
|
-
);
|
|
1211
|
-
return `${filteredContent.join(NEW_LINE)}`;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
// packages/utils/src/lib/text-formats/md/table.ts
|
|
1215
|
-
var alignString = /* @__PURE__ */ new Map([
|
|
1216
|
-
["left", ":--"],
|
|
1217
|
-
["center", ":--:"],
|
|
1218
|
-
["right", "--:"]
|
|
1219
|
-
]);
|
|
1220
|
-
function tableRow(rows) {
|
|
1221
|
-
return `|${rows.join("|")}|`;
|
|
1222
|
-
}
|
|
1223
|
-
function table2(data) {
|
|
1224
|
-
if (data.rows.length === 0) {
|
|
1225
|
-
throw new Error("Data can't be empty");
|
|
1226
|
-
}
|
|
1227
|
-
const alignmentRow = getColumnAlignments(data).map(
|
|
1228
|
-
(s) => alignString.get(s) ?? String(alignString.get("center"))
|
|
1229
|
-
);
|
|
1230
|
-
return section(
|
|
1231
|
-
`${lines(
|
|
1232
|
-
tableRow(columnsToStringArray(data)),
|
|
1233
|
-
tableRow(alignmentRow),
|
|
1234
|
-
...rowToStringArray(data).map(tableRow)
|
|
1235
|
-
)}`
|
|
1236
|
-
);
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// packages/utils/src/lib/text-formats/index.ts
|
|
1240
|
-
var md = {
|
|
1241
|
-
bold: bold2,
|
|
1242
|
-
italic: italic2,
|
|
1243
|
-
strikeThrough,
|
|
1244
|
-
code: code2,
|
|
1245
|
-
link: link2,
|
|
1246
|
-
image,
|
|
1247
|
-
headline,
|
|
1248
|
-
h,
|
|
1249
|
-
h1,
|
|
1250
|
-
h2,
|
|
1251
|
-
h3,
|
|
1252
|
-
h4,
|
|
1253
|
-
h5,
|
|
1254
|
-
h6,
|
|
1255
|
-
indentation,
|
|
1256
|
-
lines,
|
|
1257
|
-
li,
|
|
1258
|
-
section,
|
|
1259
|
-
paragraphs,
|
|
1260
|
-
table: table2
|
|
1261
|
-
};
|
|
1262
|
-
var html = {
|
|
1263
|
-
bold,
|
|
1264
|
-
italic,
|
|
1265
|
-
code,
|
|
1266
|
-
link,
|
|
1267
|
-
details,
|
|
1268
|
-
table
|
|
1269
|
-
};
|
|
1270
|
-
|
|
1271
|
-
// packages/utils/src/lib/reports/utils.ts
|
|
1272
|
-
var { image: image2, bold: boldMd } = md;
|
|
1273
|
-
function formatReportScore(score) {
|
|
1274
|
-
const scaledScore = score * 100;
|
|
1275
|
-
const roundedScore = Math.round(scaledScore);
|
|
1276
|
-
return roundedScore === 100 && score !== 1 ? Math.floor(scaledScore).toString() : roundedScore.toString();
|
|
1277
|
-
}
|
|
1278
|
-
function formatScoreWithColor(score, options) {
|
|
1279
|
-
const styledNumber = options?.skipBold ? formatReportScore(score) : boldMd(formatReportScore(score));
|
|
1280
|
-
return `${scoreMarker(score)} ${styledNumber}`;
|
|
1281
|
-
}
|
|
1282
|
-
var MARKERS = {
|
|
1283
|
-
circle: {
|
|
1284
|
-
red: "\u{1F534}",
|
|
1285
|
-
yellow: "\u{1F7E1}",
|
|
1286
|
-
green: "\u{1F7E2}"
|
|
1287
|
-
},
|
|
1288
|
-
square: {
|
|
1289
|
-
red: "\u{1F7E5}",
|
|
1290
|
-
yellow: "\u{1F7E8}",
|
|
1291
|
-
green: "\u{1F7E9}"
|
|
1292
|
-
}
|
|
1293
|
-
};
|
|
1294
|
-
function scoreMarker(score, markerType = "circle") {
|
|
1295
|
-
if (score >= SCORE_COLOR_RANGE.GREEN_MIN) {
|
|
1296
|
-
return MARKERS[markerType].green;
|
|
1297
|
-
}
|
|
1298
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
1299
|
-
return MARKERS[markerType].yellow;
|
|
1300
|
-
}
|
|
1301
|
-
return MARKERS[markerType].red;
|
|
1302
|
-
}
|
|
1303
|
-
function getDiffMarker(diff) {
|
|
1304
|
-
if (diff > 0) {
|
|
1305
|
-
return "\u2191";
|
|
1306
|
-
}
|
|
1307
|
-
if (diff < 0) {
|
|
1308
|
-
return "\u2193";
|
|
1309
|
-
}
|
|
1310
|
-
return "";
|
|
1311
|
-
}
|
|
1312
|
-
function colorByScoreDiff(text, diff) {
|
|
1313
|
-
const color = diff > 0 ? "green" : diff < 0 ? "red" : "gray";
|
|
1314
|
-
return shieldsBadge(text, color);
|
|
1315
|
-
}
|
|
1316
|
-
function shieldsBadge(text, color) {
|
|
1317
|
-
return image2(
|
|
1318
|
-
`https://img.shields.io/badge/${encodeURIComponent(text)}-${color}`,
|
|
1319
|
-
text
|
|
1320
|
-
);
|
|
1321
|
-
}
|
|
1322
|
-
function formatDiffNumber(diff) {
|
|
1323
|
-
const number = Math.abs(diff) === Number.POSITIVE_INFINITY ? "\u221E" : `${Math.abs(diff)}`;
|
|
1324
|
-
const sign = diff < 0 ? "\u2212" : "+";
|
|
1325
|
-
return `${sign}${number}`;
|
|
1326
|
-
}
|
|
1327
|
-
function severityMarker(severity) {
|
|
1328
|
-
if (severity === "error") {
|
|
1329
|
-
return "\u{1F6A8}";
|
|
1330
|
-
}
|
|
1331
|
-
if (severity === "warning") {
|
|
1332
|
-
return "\u26A0\uFE0F";
|
|
1333
|
-
}
|
|
1334
|
-
return "\u2139\uFE0F";
|
|
1335
|
-
}
|
|
1336
|
-
function calcDuration(start, stop) {
|
|
1337
|
-
return Math.round((stop ?? performance.now()) - start);
|
|
1338
|
-
}
|
|
1339
|
-
function countCategoryAudits(refs, plugins) {
|
|
1340
|
-
const groupLookup = plugins.reduce(
|
|
1341
|
-
(lookup, plugin) => {
|
|
1342
|
-
if (plugin.groups == null || plugin.groups.length === 0) {
|
|
1343
|
-
return lookup;
|
|
1344
|
-
}
|
|
1345
|
-
return {
|
|
1346
|
-
...lookup,
|
|
1347
|
-
[plugin.slug]: Object.fromEntries(
|
|
1348
|
-
plugin.groups.map((group) => [group.slug, group])
|
|
1349
|
-
)
|
|
1350
|
-
};
|
|
1351
|
-
},
|
|
1352
|
-
{}
|
|
1353
|
-
);
|
|
1354
|
-
return refs.reduce((acc, ref) => {
|
|
1355
|
-
if (ref.type === "group") {
|
|
1356
|
-
const groupRefs = groupLookup[ref.plugin]?.[ref.slug]?.refs;
|
|
1357
|
-
return acc + (groupRefs?.length ?? 0);
|
|
1358
|
-
}
|
|
1359
|
-
return acc + 1;
|
|
1360
|
-
}, 0);
|
|
1361
|
-
}
|
|
1362
|
-
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1363
|
-
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1364
|
-
if (!auditPlugin) {
|
|
1365
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1366
|
-
}
|
|
1367
|
-
const audit = auditPlugin.audits.find(
|
|
1368
|
-
({ slug: auditSlug }) => auditSlug === slug
|
|
1369
|
-
);
|
|
1370
|
-
if (!audit) {
|
|
1371
|
-
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1372
|
-
}
|
|
1373
|
-
return {
|
|
1374
|
-
...audit,
|
|
1375
|
-
weight,
|
|
1376
|
-
plugin
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1380
|
-
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1381
|
-
if (!groupPlugin) {
|
|
1382
|
-
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1383
|
-
}
|
|
1384
|
-
const group = groupPlugin.groups?.find(
|
|
1385
|
-
({ slug: groupSlug }) => groupSlug === slug
|
|
1386
|
-
);
|
|
1387
|
-
if (!group) {
|
|
1388
|
-
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1389
|
-
}
|
|
1390
|
-
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1391
|
-
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1392
|
-
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1393
|
-
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1394
|
-
return aIndex - bIndex;
|
|
1395
|
-
});
|
|
1396
|
-
return {
|
|
1397
|
-
...group,
|
|
1398
|
-
refs: sortedAuditRefs,
|
|
1399
|
-
plugin,
|
|
1400
|
-
weight
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1404
|
-
return group.refs.map(
|
|
1405
|
-
(ref) => getSortableAuditByRef(
|
|
1406
|
-
{
|
|
1407
|
-
plugin,
|
|
1408
|
-
slug: ref.slug,
|
|
1409
|
-
weight: ref.weight,
|
|
1410
|
-
type: "audit"
|
|
1411
|
-
},
|
|
1412
|
-
plugins
|
|
1413
|
-
)
|
|
1414
|
-
).sort(compareCategoryAuditsAndGroups);
|
|
1415
|
-
}
|
|
1416
|
-
function compareCategoryAuditsAndGroups(a, b) {
|
|
1417
|
-
if (a.weight !== b.weight) {
|
|
1418
|
-
return b.weight - a.weight;
|
|
1419
|
-
}
|
|
1420
|
-
if (a.score !== b.score) {
|
|
1421
|
-
return a.score - b.score;
|
|
1422
|
-
}
|
|
1423
|
-
if ("value" in a && "value" in b && a.value !== b.value) {
|
|
1424
|
-
return b.value - a.value;
|
|
1425
|
-
}
|
|
1426
|
-
return a.title.localeCompare(b.title);
|
|
1427
|
-
}
|
|
1428
|
-
function compareAudits(a, b) {
|
|
1429
|
-
if (a.score !== b.score) {
|
|
1430
|
-
return a.score - b.score;
|
|
1431
|
-
}
|
|
1432
|
-
if (a.value !== b.value) {
|
|
1433
|
-
return b.value - a.value;
|
|
1434
|
-
}
|
|
1435
|
-
return a.title.localeCompare(b.title);
|
|
1436
|
-
}
|
|
1437
|
-
function compareIssueSeverity(severity1, severity2) {
|
|
1438
|
-
const levels = {
|
|
1439
|
-
info: 0,
|
|
1440
|
-
warning: 1,
|
|
1441
|
-
error: 2
|
|
1442
|
-
};
|
|
1443
|
-
return levels[severity1] - levels[severity2];
|
|
1444
|
-
}
|
|
1445
|
-
async function loadReport(options) {
|
|
1446
|
-
const { outputDir, filename, format } = options;
|
|
1447
|
-
await ensureDirectoryExists(outputDir);
|
|
1448
|
-
const filePath = join(outputDir, `${filename}.${format}`);
|
|
1449
|
-
if (format === "json") {
|
|
1450
|
-
const content = await readJsonFile(filePath);
|
|
1451
|
-
return reportSchema.parse(content);
|
|
1452
|
-
}
|
|
1453
|
-
const text = await readTextFile(filePath);
|
|
1454
|
-
return text;
|
|
1455
|
-
}
|
|
1456
|
-
function throwIsNotPresentError(itemName, presentPlace) {
|
|
1457
|
-
throw new Error(`${itemName} is not present in ${presentPlace}`);
|
|
1458
|
-
}
|
|
1459
|
-
function getPluginNameFromSlug(slug, plugins) {
|
|
1460
|
-
return plugins.find(({ slug: pluginSlug }) => pluginSlug === slug)?.title || slug;
|
|
1461
|
-
}
|
|
1462
|
-
function compareIssues(a, b) {
|
|
1463
|
-
if (a.severity !== b.severity) {
|
|
1464
|
-
return -compareIssueSeverity(a.severity, b.severity);
|
|
1465
|
-
}
|
|
1466
|
-
if (!a.source && b.source) {
|
|
1467
|
-
return -1;
|
|
1468
|
-
}
|
|
1469
|
-
if (a.source && !b.source) {
|
|
1470
|
-
return 1;
|
|
1471
|
-
}
|
|
1472
|
-
if (a.source?.file !== b.source?.file) {
|
|
1473
|
-
return a.source?.file.localeCompare(b.source?.file || "") ?? 0;
|
|
1474
|
-
}
|
|
1475
|
-
if (!a.source?.position && b.source?.position) {
|
|
1476
|
-
return -1;
|
|
1477
|
-
}
|
|
1478
|
-
if (a.source?.position && !b.source?.position) {
|
|
1479
|
-
return 1;
|
|
1480
|
-
}
|
|
1481
|
-
if (a.source?.position?.startLine !== b.source?.position?.startLine) {
|
|
1482
|
-
return (a.source?.position?.startLine ?? 0) - (b.source?.position?.startLine ?? 0);
|
|
1483
|
-
}
|
|
1484
|
-
return 0;
|
|
1187
|
+
function deepClone(obj) {
|
|
1188
|
+
return obj == null || typeof obj !== "object" ? obj : structuredClone(obj);
|
|
1485
1189
|
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
super(result.stderr);
|
|
1494
|
-
this.code = result.code;
|
|
1495
|
-
this.stderr = result.stderr;
|
|
1496
|
-
this.stdout = result.stdout;
|
|
1497
|
-
}
|
|
1498
|
-
};
|
|
1499
|
-
function executeProcess(cfg) {
|
|
1500
|
-
const { observer, cwd, command, args, ignoreExitCode = false } = cfg;
|
|
1501
|
-
const { onStdout, onError, onComplete } = observer ?? {};
|
|
1502
|
-
const date = (/* @__PURE__ */ new Date()).toISOString();
|
|
1503
|
-
const start = performance.now();
|
|
1504
|
-
return new Promise((resolve, reject) => {
|
|
1505
|
-
const process2 = spawn(command, args, { cwd, shell: true });
|
|
1506
|
-
let stdout = "";
|
|
1507
|
-
let stderr = "";
|
|
1508
|
-
process2.stdout.on("data", (data) => {
|
|
1509
|
-
stdout += String(data);
|
|
1510
|
-
onStdout?.(String(data));
|
|
1511
|
-
});
|
|
1512
|
-
process2.stderr.on("data", (data) => {
|
|
1513
|
-
stderr += String(data);
|
|
1514
|
-
});
|
|
1515
|
-
process2.on("error", (err) => {
|
|
1516
|
-
stderr += err.toString();
|
|
1517
|
-
});
|
|
1518
|
-
process2.on("close", (code3) => {
|
|
1519
|
-
const timings = { date, duration: calcDuration(start) };
|
|
1520
|
-
if (code3 === 0 || ignoreExitCode) {
|
|
1521
|
-
onComplete?.();
|
|
1522
|
-
resolve({ code: code3, stdout, stderr, ...timings });
|
|
1523
|
-
} else {
|
|
1524
|
-
const errorMsg = new ProcessError({ code: code3, stdout, stderr, ...timings });
|
|
1525
|
-
onError?.(errorMsg);
|
|
1526
|
-
reject(errorMsg);
|
|
1527
|
-
}
|
|
1528
|
-
});
|
|
1529
|
-
});
|
|
1190
|
+
function toUnixPath(path) {
|
|
1191
|
+
return path.replace(/\\/g, "/");
|
|
1192
|
+
}
|
|
1193
|
+
function capitalize(text) {
|
|
1194
|
+
return `${text.charAt(0).toLocaleUpperCase()}${text.slice(
|
|
1195
|
+
1
|
|
1196
|
+
)}`;
|
|
1530
1197
|
}
|
|
1531
1198
|
|
|
1532
1199
|
// packages/utils/src/lib/git/git.ts
|
|
1533
|
-
import { isAbsolute, join as join2, relative } from "node:path";
|
|
1534
|
-
import { simpleGit } from "simple-git";
|
|
1535
1200
|
function getGitRoot(git = simpleGit()) {
|
|
1536
1201
|
return git.revparse("--show-toplevel");
|
|
1537
1202
|
}
|
|
1538
1203
|
function formatGitPath(path, gitRoot) {
|
|
1539
|
-
const absolutePath = isAbsolute(path) ? path :
|
|
1204
|
+
const absolutePath = isAbsolute(path) ? path : join(process.cwd(), path);
|
|
1540
1205
|
const relativePath = relative(gitRoot, absolutePath);
|
|
1541
1206
|
return toUnixPath(relativePath);
|
|
1542
1207
|
}
|
|
@@ -1618,17 +1283,17 @@ function groupByStatus(results) {
|
|
|
1618
1283
|
}
|
|
1619
1284
|
|
|
1620
1285
|
// packages/utils/src/lib/progress.ts
|
|
1621
|
-
import
|
|
1286
|
+
import { black, bold as bold2, gray as gray2, green } from "ansis";
|
|
1622
1287
|
import { MultiProgressBars } from "multi-progress-bars";
|
|
1623
1288
|
var barStyles = {
|
|
1624
|
-
active: (s) =>
|
|
1625
|
-
done: (s) =>
|
|
1626
|
-
idle: (s) =>
|
|
1289
|
+
active: (s) => green(s),
|
|
1290
|
+
done: (s) => gray2(s),
|
|
1291
|
+
idle: (s) => gray2(s)
|
|
1627
1292
|
};
|
|
1628
1293
|
var messageStyles = {
|
|
1629
|
-
active: (s) =>
|
|
1630
|
-
done: (s) =>
|
|
1631
|
-
idle: (s) =>
|
|
1294
|
+
active: (s) => black(s),
|
|
1295
|
+
done: (s) => bold2.green(s),
|
|
1296
|
+
idle: (s) => gray2(s)
|
|
1632
1297
|
};
|
|
1633
1298
|
var mpb;
|
|
1634
1299
|
function getSingletonProgressBars(options) {
|
|
@@ -1671,334 +1336,502 @@ function getProgressBar(taskName) {
|
|
|
1671
1336
|
}
|
|
1672
1337
|
};
|
|
1673
1338
|
}
|
|
1674
|
-
|
|
1675
|
-
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1676
|
-
function listGroupsFromAllPlugins(report) {
|
|
1677
|
-
return report.plugins.flatMap(
|
|
1678
|
-
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
function listAuditsFromAllPlugins(report) {
|
|
1682
|
-
return report.plugins.flatMap(
|
|
1683
|
-
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1684
|
-
);
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
// packages/utils/src/lib/reports/
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1339
|
+
|
|
1340
|
+
// packages/utils/src/lib/reports/flatten-plugins.ts
|
|
1341
|
+
function listGroupsFromAllPlugins(report) {
|
|
1342
|
+
return report.plugins.flatMap(
|
|
1343
|
+
(plugin) => plugin.groups?.map((group) => ({ plugin, group })) ?? []
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
function listAuditsFromAllPlugins(report) {
|
|
1347
|
+
return report.plugins.flatMap(
|
|
1348
|
+
(plugin) => plugin.audits.map((audit) => ({ plugin, audit }))
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1353
|
+
import { MarkdownDocument as MarkdownDocument3, md as md4 } from "build-md";
|
|
1354
|
+
|
|
1355
|
+
// packages/utils/src/lib/text-formats/constants.ts
|
|
1356
|
+
var HIERARCHY = {
|
|
1357
|
+
level_1: 1,
|
|
1358
|
+
level_2: 2,
|
|
1359
|
+
level_3: 3,
|
|
1360
|
+
level_4: 4,
|
|
1361
|
+
level_5: 5,
|
|
1362
|
+
level_6: 6
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
// packages/utils/src/lib/text-formats/table.ts
|
|
1366
|
+
function rowToStringArray({ rows, columns = [] }) {
|
|
1367
|
+
if (Array.isArray(rows.at(0)) && typeof columns.at(0) === "object") {
|
|
1368
|
+
throw new TypeError(
|
|
1369
|
+
"Column can`t be object when rows are primitive values"
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
return rows.map((row) => {
|
|
1373
|
+
if (Array.isArray(row)) {
|
|
1374
|
+
return row.map(String);
|
|
1375
|
+
}
|
|
1376
|
+
const objectRow = row;
|
|
1377
|
+
if (columns.length === 0 || typeof columns.at(0) === "string") {
|
|
1378
|
+
return Object.values(objectRow).map(
|
|
1379
|
+
(value) => value == null ? "" : String(value)
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
return columns.map(
|
|
1383
|
+
({ key }) => objectRow[key] == null ? "" : String(objectRow[key])
|
|
1384
|
+
);
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
function columnsToStringArray({
|
|
1388
|
+
rows,
|
|
1389
|
+
columns = []
|
|
1390
|
+
}) {
|
|
1391
|
+
const firstRow = rows.at(0);
|
|
1392
|
+
const primitiveRows = Array.isArray(firstRow);
|
|
1393
|
+
if (typeof columns.at(0) === "string" && !primitiveRows) {
|
|
1394
|
+
throw new Error("invalid union type. Caught by model parsing.");
|
|
1395
|
+
}
|
|
1396
|
+
if (columns.length === 0) {
|
|
1397
|
+
if (Array.isArray(firstRow)) {
|
|
1398
|
+
return firstRow.map((_, idx) => String(idx));
|
|
1399
|
+
}
|
|
1400
|
+
return Object.keys(firstRow);
|
|
1401
|
+
}
|
|
1402
|
+
if (typeof columns.at(0) === "string") {
|
|
1403
|
+
return columns.map(String);
|
|
1404
|
+
}
|
|
1405
|
+
const cols = columns;
|
|
1406
|
+
return cols.map(({ label, key }) => label ?? capitalize(key));
|
|
1407
|
+
}
|
|
1408
|
+
function getColumnAlignmentForKeyAndIndex(targetKey, targetIdx, columns = []) {
|
|
1409
|
+
const column = columns.at(targetIdx) ?? columns.find((col) => col.key === targetKey);
|
|
1410
|
+
if (typeof column === "string") {
|
|
1411
|
+
return column;
|
|
1412
|
+
} else if (typeof column === "object") {
|
|
1413
|
+
return column.align ?? "center";
|
|
1414
|
+
} else {
|
|
1415
|
+
return "center";
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function getColumnAlignmentForIndex(targetIdx, columns = []) {
|
|
1419
|
+
const column = columns.at(targetIdx);
|
|
1420
|
+
if (column == null) {
|
|
1421
|
+
return "center";
|
|
1422
|
+
} else if (typeof column === "string") {
|
|
1423
|
+
return column;
|
|
1424
|
+
} else if (typeof column === "object") {
|
|
1425
|
+
return column.align ?? "center";
|
|
1426
|
+
} else {
|
|
1427
|
+
return "center";
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function getColumnAlignments(tableData) {
|
|
1431
|
+
const { rows, columns = [] } = tableData;
|
|
1432
|
+
if (rows.at(0) == null) {
|
|
1433
|
+
throw new Error("first row can`t be undefined.");
|
|
1434
|
+
}
|
|
1435
|
+
if (Array.isArray(rows.at(0))) {
|
|
1436
|
+
const firstPrimitiveRow = rows.at(0);
|
|
1437
|
+
return Array.from({ length: firstPrimitiveRow.length }).map(
|
|
1438
|
+
(_, idx) => getColumnAlignmentForIndex(idx, columns)
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
const biggestRow = [...rows].sort((a, b) => Object.keys(a).length - Object.keys(b).length).at(-1);
|
|
1442
|
+
if (columns.length > 0) {
|
|
1443
|
+
return columns.map(
|
|
1444
|
+
(column, idx) => typeof column === "string" ? column : getColumnAlignmentForKeyAndIndex(
|
|
1445
|
+
column.key,
|
|
1446
|
+
idx,
|
|
1447
|
+
columns
|
|
1448
|
+
)
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
return Object.keys(biggestRow ?? {}).map((_) => "center");
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
// packages/utils/src/lib/reports/formatting.ts
|
|
1455
|
+
import { MarkdownDocument, md as md2 } from "build-md";
|
|
1456
|
+
function tableSection(tableData, options) {
|
|
1457
|
+
if (tableData.rows.length === 0) {
|
|
1458
|
+
return null;
|
|
1459
|
+
}
|
|
1460
|
+
const { level = HIERARCHY.level_4 } = options ?? {};
|
|
1461
|
+
const columns = columnsToStringArray(tableData);
|
|
1462
|
+
const alignments = getColumnAlignments(tableData);
|
|
1463
|
+
const rows = rowToStringArray(tableData);
|
|
1464
|
+
return new MarkdownDocument().heading(level, tableData.title).table(
|
|
1465
|
+
columns.map((heading, i) => {
|
|
1466
|
+
const alignment = alignments[i];
|
|
1467
|
+
if (alignment) {
|
|
1468
|
+
return { heading, alignment };
|
|
1469
|
+
}
|
|
1470
|
+
return heading;
|
|
1471
|
+
}),
|
|
1472
|
+
rows
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
function metaDescription(audit) {
|
|
1476
|
+
const docsUrl = audit.docsUrl;
|
|
1477
|
+
const description = audit.description?.trim();
|
|
1478
|
+
if (docsUrl) {
|
|
1479
|
+
const docsLink = md2.link(docsUrl, "\u{1F4D6} Docs");
|
|
1480
|
+
if (!description) {
|
|
1481
|
+
return docsLink;
|
|
1482
|
+
}
|
|
1483
|
+
const parsedDescription = description.endsWith("```") ? `${description}
|
|
1484
|
+
|
|
1485
|
+
` : `${description} `;
|
|
1486
|
+
return md2`${parsedDescription}${docsLink}`;
|
|
1487
|
+
}
|
|
1488
|
+
if (description && description.trim().length > 0) {
|
|
1489
|
+
return description;
|
|
1490
|
+
}
|
|
1491
|
+
return "";
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1495
|
+
import { MarkdownDocument as MarkdownDocument2, md as md3 } from "build-md";
|
|
1496
|
+
|
|
1497
|
+
// packages/utils/src/lib/reports/sorting.ts
|
|
1498
|
+
function getSortableAuditByRef({ slug, weight, plugin }, plugins) {
|
|
1499
|
+
const auditPlugin = plugins.find((p) => p.slug === plugin);
|
|
1500
|
+
if (!auditPlugin) {
|
|
1501
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1502
|
+
}
|
|
1503
|
+
const audit = auditPlugin.audits.find(
|
|
1504
|
+
({ slug: auditSlug }) => auditSlug === slug
|
|
1505
|
+
);
|
|
1506
|
+
if (!audit) {
|
|
1507
|
+
throwIsNotPresentError(`Audit ${slug}`, auditPlugin.slug);
|
|
1508
|
+
}
|
|
1509
|
+
return {
|
|
1510
|
+
...audit,
|
|
1511
|
+
weight,
|
|
1512
|
+
plugin
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
function getSortedGroupAudits(group, plugin, plugins) {
|
|
1516
|
+
return group.refs.map(
|
|
1517
|
+
(ref) => getSortableAuditByRef(
|
|
1518
|
+
{
|
|
1519
|
+
plugin,
|
|
1520
|
+
slug: ref.slug,
|
|
1521
|
+
weight: ref.weight,
|
|
1522
|
+
type: "audit"
|
|
1523
|
+
},
|
|
1524
|
+
plugins
|
|
1525
|
+
)
|
|
1526
|
+
).sort(compareCategoryAuditsAndGroups);
|
|
1527
|
+
}
|
|
1528
|
+
function getSortableGroupByRef({ plugin, slug, weight }, plugins) {
|
|
1529
|
+
const groupPlugin = plugins.find((p) => p.slug === plugin);
|
|
1530
|
+
if (!groupPlugin) {
|
|
1531
|
+
throwIsNotPresentError(`Plugin ${plugin}`, "report");
|
|
1532
|
+
}
|
|
1533
|
+
const group = groupPlugin.groups?.find(
|
|
1534
|
+
({ slug: groupSlug }) => groupSlug === slug
|
|
1535
|
+
);
|
|
1536
|
+
if (!group) {
|
|
1537
|
+
throwIsNotPresentError(`Group ${slug}`, groupPlugin.slug);
|
|
1538
|
+
}
|
|
1539
|
+
const sortedAudits = getSortedGroupAudits(group, groupPlugin.slug, plugins);
|
|
1540
|
+
const sortedAuditRefs = [...group.refs].sort((a, b) => {
|
|
1541
|
+
const aIndex = sortedAudits.findIndex((ref) => ref.slug === a.slug);
|
|
1542
|
+
const bIndex = sortedAudits.findIndex((ref) => ref.slug === b.slug);
|
|
1543
|
+
return aIndex - bIndex;
|
|
1544
|
+
});
|
|
1545
|
+
return {
|
|
1546
|
+
...group,
|
|
1547
|
+
refs: sortedAuditRefs,
|
|
1548
|
+
plugin,
|
|
1549
|
+
weight
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
function sortReport(report) {
|
|
1553
|
+
const { categories, plugins } = report;
|
|
1554
|
+
const sortedCategories = categories.map((category) => {
|
|
1555
|
+
const { audits, groups } = category.refs.reduce(
|
|
1556
|
+
(acc, ref) => ({
|
|
1557
|
+
...acc,
|
|
1558
|
+
...ref.type === "group" ? {
|
|
1559
|
+
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
1560
|
+
} : {
|
|
1561
|
+
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
1562
|
+
}
|
|
1563
|
+
}),
|
|
1564
|
+
{ groups: [], audits: [] }
|
|
1565
|
+
);
|
|
1566
|
+
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
1567
|
+
compareCategoryAuditsAndGroups
|
|
1568
|
+
);
|
|
1569
|
+
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
1570
|
+
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
1571
|
+
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
1572
|
+
);
|
|
1573
|
+
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
1574
|
+
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
1575
|
+
);
|
|
1576
|
+
return aIndex - bIndex;
|
|
1577
|
+
});
|
|
1578
|
+
return { ...category, refs: sortedRefs };
|
|
1579
|
+
});
|
|
1580
|
+
return {
|
|
1581
|
+
...report,
|
|
1582
|
+
categories: sortedCategories,
|
|
1583
|
+
plugins: sortPlugins(plugins)
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function sortPlugins(plugins) {
|
|
1587
|
+
return plugins.map((plugin) => ({
|
|
1588
|
+
...plugin,
|
|
1589
|
+
audits: [...plugin.audits].sort(compareAudits).map(
|
|
1590
|
+
(audit) => audit.details?.issues ? {
|
|
1591
|
+
...audit,
|
|
1592
|
+
details: {
|
|
1593
|
+
...audit.details,
|
|
1594
|
+
issues: [...audit.details.issues].sort(compareIssues)
|
|
1595
|
+
}
|
|
1596
|
+
} : audit
|
|
1597
|
+
)
|
|
1598
|
+
}));
|
|
1716
1599
|
}
|
|
1717
1600
|
|
|
1718
1601
|
// packages/utils/src/lib/reports/generate-md-report-categoy-section.ts
|
|
1719
|
-
var { link: link4, section: section3, h2: h22, lines: lines3, li: li2, bold: boldMd2, h3: h32, indentation: indentation2 } = md;
|
|
1720
1602
|
function categoriesOverviewSection(report) {
|
|
1721
1603
|
const { categories, plugins } = report;
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1604
|
+
return new MarkdownDocument2().table(
|
|
1605
|
+
[
|
|
1606
|
+
{ heading: "\u{1F3F7} Category", alignment: "left" },
|
|
1607
|
+
{ heading: "\u2B50 Score", alignment: "center" },
|
|
1608
|
+
{ heading: "\u{1F6E1} Audits", alignment: "center" }
|
|
1609
|
+
],
|
|
1610
|
+
categories.map(({ title, refs, score, isBinary }) => [
|
|
1611
|
+
// @TODO refactor `isBinary: boolean` to `targetScore: number` #713
|
|
1612
|
+
// The heading "ID" is inferred from the heading text in Markdown.
|
|
1613
|
+
md3.link(`#${slugify(title)}`, title),
|
|
1614
|
+
md3`${scoreMarker(score)} ${md3.bold(
|
|
1615
|
+
formatReportScore(score)
|
|
1616
|
+
)}${binaryIconSuffix(score, isBinary)}`,
|
|
1617
|
+
countCategoryAudits(refs, plugins).toString()
|
|
1618
|
+
])
|
|
1619
|
+
);
|
|
1737
1620
|
}
|
|
1738
1621
|
function categoriesDetailsSection(report) {
|
|
1739
1622
|
const { categories, plugins } = report;
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
category.score
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
...categoryMDItems
|
|
1767
|
-
);
|
|
1768
|
-
});
|
|
1769
|
-
return lines3(h22(CATEGORIES_TITLE), ...categoryDetails);
|
|
1623
|
+
return new MarkdownDocument2().heading(HIERARCHY.level_2, "\u{1F3F7} Categories").$foreach(
|
|
1624
|
+
categories,
|
|
1625
|
+
(doc, category) => doc.heading(HIERARCHY.level_3, category.title).paragraph(metaDescription(category)).paragraph(
|
|
1626
|
+
md3`${scoreMarker(category.score)} Score: ${md3.bold(
|
|
1627
|
+
formatReportScore(category.score)
|
|
1628
|
+
)}${binaryIconSuffix(category.score, category.isBinary)}`
|
|
1629
|
+
).list(
|
|
1630
|
+
category.refs.map((ref) => {
|
|
1631
|
+
if (ref.type === "group") {
|
|
1632
|
+
const group = getSortableGroupByRef(ref, plugins);
|
|
1633
|
+
const groupAudits = group.refs.map(
|
|
1634
|
+
(groupRef) => getSortableAuditByRef(
|
|
1635
|
+
{ ...groupRef, plugin: group.plugin, type: "audit" },
|
|
1636
|
+
plugins
|
|
1637
|
+
)
|
|
1638
|
+
);
|
|
1639
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1640
|
+
return categoryGroupItem(group, groupAudits, pluginTitle);
|
|
1641
|
+
} else {
|
|
1642
|
+
const audit = getSortableAuditByRef(ref, plugins);
|
|
1643
|
+
const pluginTitle = getPluginNameFromSlug(ref.plugin, plugins);
|
|
1644
|
+
return categoryRef(audit, pluginTitle);
|
|
1645
|
+
}
|
|
1646
|
+
})
|
|
1647
|
+
)
|
|
1648
|
+
);
|
|
1770
1649
|
}
|
|
1771
1650
|
function categoryRef({ title, score, value, displayValue }, pluginTitle) {
|
|
1772
|
-
const auditTitleAsLink =
|
|
1651
|
+
const auditTitleAsLink = md3.link(
|
|
1773
1652
|
`#${slugify(title)}-${slugify(pluginTitle)}`,
|
|
1774
1653
|
title
|
|
1775
1654
|
);
|
|
1776
1655
|
const marker = scoreMarker(score, "square");
|
|
1777
|
-
return
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
)}`
|
|
1781
|
-
);
|
|
1656
|
+
return md3`${marker} ${auditTitleAsLink} (${md3.italic(
|
|
1657
|
+
pluginTitle
|
|
1658
|
+
)}) - ${md3.bold((displayValue || value).toString())}`;
|
|
1782
1659
|
}
|
|
1783
1660
|
function categoryGroupItem({ score = 0, title }, groupAudits, pluginTitle) {
|
|
1784
|
-
const groupTitle =
|
|
1785
|
-
|
|
1786
|
-
)
|
|
1787
|
-
const
|
|
1788
|
-
(
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1661
|
+
const groupTitle = md3`${scoreMarker(score)} ${title} (${md3.italic(
|
|
1662
|
+
pluginTitle
|
|
1663
|
+
)})`;
|
|
1664
|
+
const auditsList = md3.list(
|
|
1665
|
+
groupAudits.map(
|
|
1666
|
+
({ title: auditTitle, score: auditScore, value, displayValue }) => {
|
|
1667
|
+
const auditTitleLink = md3.link(
|
|
1668
|
+
`#${slugify(auditTitle)}-${slugify(pluginTitle)}`,
|
|
1669
|
+
auditTitle
|
|
1670
|
+
);
|
|
1671
|
+
const marker = scoreMarker(auditScore, "square");
|
|
1672
|
+
return md3`${marker} ${auditTitleLink} - ${md3.bold(
|
|
1673
|
+
String(displayValue ?? value)
|
|
1674
|
+
)}`;
|
|
1675
|
+
}
|
|
1676
|
+
)
|
|
1802
1677
|
);
|
|
1803
|
-
return
|
|
1678
|
+
return md3`${groupTitle}${auditsList}`;
|
|
1679
|
+
}
|
|
1680
|
+
function binaryIconSuffix(score, isBinary) {
|
|
1681
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, { prefix: " " });
|
|
1804
1682
|
}
|
|
1805
1683
|
|
|
1806
1684
|
// packages/utils/src/lib/reports/generate-md-report.ts
|
|
1807
|
-
var { h1: h12, h2: h23, h3: h33, lines: lines4, link: link5, section: section4, code: codeMd } = md;
|
|
1808
|
-
var { bold: boldHtml, details: details2 } = html;
|
|
1809
1685
|
function auditDetailsAuditValue({
|
|
1810
1686
|
score,
|
|
1811
1687
|
value,
|
|
1812
1688
|
displayValue
|
|
1813
1689
|
}) {
|
|
1814
|
-
return `${scoreMarker(score, "square")} ${
|
|
1690
|
+
return md4`${scoreMarker(score, "square")} ${md4.bold(
|
|
1815
1691
|
String(displayValue ?? value)
|
|
1816
1692
|
)} (score: ${formatReportScore(score)})`;
|
|
1817
1693
|
}
|
|
1818
1694
|
function generateMdReport(report) {
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
`${FOOTER_PREFIX}${SPACE}${link5(README_LINK, "Code PushUp")}`
|
|
1827
|
-
);
|
|
1695
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_1, REPORT_HEADLINE_TEXT).$if(
|
|
1696
|
+
report.categories.length > 0,
|
|
1697
|
+
(doc) => doc.$concat(
|
|
1698
|
+
categoriesOverviewSection(report),
|
|
1699
|
+
categoriesDetailsSection(report)
|
|
1700
|
+
)
|
|
1701
|
+
).$concat(auditsSection(report), aboutSection(report)).rule().paragraph(md4`${FOOTER_PREFIX} ${md4.link(README_LINK, "Code PushUp")}`).toString();
|
|
1828
1702
|
}
|
|
1829
1703
|
function auditDetailsIssues(issues = []) {
|
|
1830
1704
|
if (issues.length === 0) {
|
|
1831
|
-
return
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
return { severity, message, file, line: "" };
|
|
1845
|
-
}
|
|
1846
|
-
const { startLine, endLine } = sourceVal.position;
|
|
1847
|
-
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
1848
|
-
return { severity, message, file, line };
|
|
1705
|
+
return null;
|
|
1706
|
+
}
|
|
1707
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_4, "Issues").table(
|
|
1708
|
+
[
|
|
1709
|
+
{ heading: "Severity", alignment: "center" },
|
|
1710
|
+
{ heading: "Message", alignment: "left" },
|
|
1711
|
+
{ heading: "Source file", alignment: "left" },
|
|
1712
|
+
{ heading: "Line(s)", alignment: "center" }
|
|
1713
|
+
],
|
|
1714
|
+
issues.map(({ severity: level, message, source }) => {
|
|
1715
|
+
const severity = md4`${severityMarker(level)} ${md4.italic(level)}`;
|
|
1716
|
+
if (!source) {
|
|
1717
|
+
return [severity, message];
|
|
1849
1718
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1719
|
+
const file = md4.code(source.file);
|
|
1720
|
+
if (!source.position) {
|
|
1721
|
+
return [severity, message, file];
|
|
1722
|
+
}
|
|
1723
|
+
const { startLine, endLine } = source.position;
|
|
1724
|
+
const line = `${startLine || ""}${endLine && startLine !== endLine ? `-${endLine}` : ""}`;
|
|
1725
|
+
return [severity, message, file, line];
|
|
1726
|
+
})
|
|
1727
|
+
);
|
|
1853
1728
|
}
|
|
1854
1729
|
function auditDetails(audit) {
|
|
1855
|
-
const { table:
|
|
1730
|
+
const { table: table2, issues = [] } = audit.details ?? {};
|
|
1856
1731
|
const detailsValue = auditDetailsAuditValue(audit);
|
|
1857
|
-
if (issues.length === 0 &&
|
|
1858
|
-
return
|
|
1732
|
+
if (issues.length === 0 && !table2?.rows.length) {
|
|
1733
|
+
return new MarkdownDocument3().paragraph(detailsValue);
|
|
1859
1734
|
}
|
|
1860
|
-
const tableSectionContent =
|
|
1861
|
-
const issuesSectionContent = issues.length > 0
|
|
1862
|
-
return
|
|
1735
|
+
const tableSectionContent = table2 && tableSection(table2);
|
|
1736
|
+
const issuesSectionContent = issues.length > 0 && auditDetailsIssues(issues);
|
|
1737
|
+
return new MarkdownDocument3().details(
|
|
1863
1738
|
detailsValue,
|
|
1864
|
-
|
|
1739
|
+
new MarkdownDocument3().$concat(tableSectionContent, issuesSectionContent)
|
|
1865
1740
|
);
|
|
1866
1741
|
}
|
|
1867
1742
|
function auditsSection({
|
|
1868
1743
|
plugins
|
|
1869
1744
|
}) {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1745
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$foreach(
|
|
1746
|
+
plugins.flatMap(
|
|
1747
|
+
(plugin) => plugin.audits.map((audit) => ({ ...audit, plugin }))
|
|
1748
|
+
),
|
|
1749
|
+
(doc, { plugin, ...audit }) => {
|
|
1750
|
+
const auditTitle = `${audit.title} (${plugin.title})`;
|
|
1876
1751
|
const detailsContent = auditDetails(audit);
|
|
1877
1752
|
const descriptionContent = metaDescription(audit);
|
|
1878
|
-
return
|
|
1879
|
-
}
|
|
1753
|
+
return doc.heading(HIERARCHY.level_3, auditTitle).$concat(detailsContent).paragraph(descriptionContent);
|
|
1754
|
+
}
|
|
1880
1755
|
);
|
|
1881
|
-
return section4(h23("\u{1F6E1}\uFE0F Audits"), ...content);
|
|
1882
1756
|
}
|
|
1883
1757
|
function aboutSection(report) {
|
|
1884
1758
|
const { date, plugins } = report;
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
{
|
|
1902
|
-
key: "plugin",
|
|
1903
|
-
align: "left"
|
|
1904
|
-
},
|
|
1905
|
-
{
|
|
1906
|
-
key: "audits"
|
|
1907
|
-
},
|
|
1908
|
-
{
|
|
1909
|
-
key: "version"
|
|
1910
|
-
},
|
|
1911
|
-
{
|
|
1912
|
-
key: "duration"
|
|
1913
|
-
}
|
|
1759
|
+
return new MarkdownDocument3().heading(HIERARCHY.level_2, "About").paragraph(
|
|
1760
|
+
md4`Report was created by ${md4.link(
|
|
1761
|
+
README_LINK,
|
|
1762
|
+
"Code PushUp"
|
|
1763
|
+
)} on ${formatDate(new Date(date))}.`
|
|
1764
|
+
).table(...pluginMetaTable({ plugins })).table(...reportMetaTable(report));
|
|
1765
|
+
}
|
|
1766
|
+
function pluginMetaTable({
|
|
1767
|
+
plugins
|
|
1768
|
+
}) {
|
|
1769
|
+
return [
|
|
1770
|
+
[
|
|
1771
|
+
{ heading: "Plugin", alignment: "left" },
|
|
1772
|
+
{ heading: "Audits", alignment: "center" },
|
|
1773
|
+
{ heading: "Version", alignment: "center" },
|
|
1774
|
+
{ heading: "Duration", alignment: "right" }
|
|
1914
1775
|
],
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
plugin: pluginTitle,
|
|
1923
|
-
audits: audits.length.toString(),
|
|
1924
|
-
version: codeMd(pluginVersion || ""),
|
|
1925
|
-
duration: formatDuration(pluginDuration)
|
|
1926
|
-
})
|
|
1927
|
-
)
|
|
1928
|
-
};
|
|
1776
|
+
plugins.map(({ title, audits, version: version2 = "", duration }) => [
|
|
1777
|
+
title,
|
|
1778
|
+
audits.length.toString(),
|
|
1779
|
+
version2 && md4.code(version2),
|
|
1780
|
+
formatDuration(duration)
|
|
1781
|
+
])
|
|
1782
|
+
];
|
|
1929
1783
|
}
|
|
1930
|
-
function
|
|
1784
|
+
function reportMetaTable({
|
|
1931
1785
|
commit,
|
|
1932
1786
|
version: version2,
|
|
1933
1787
|
duration,
|
|
1934
1788
|
plugins,
|
|
1935
1789
|
categories
|
|
1936
1790
|
}) {
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
{
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
key: "version"
|
|
1946
|
-
},
|
|
1947
|
-
{
|
|
1948
|
-
key: "duration"
|
|
1949
|
-
},
|
|
1950
|
-
{
|
|
1951
|
-
key: "plugins"
|
|
1952
|
-
},
|
|
1953
|
-
{
|
|
1954
|
-
key: "categories"
|
|
1955
|
-
},
|
|
1956
|
-
{
|
|
1957
|
-
key: "audits"
|
|
1958
|
-
}
|
|
1791
|
+
return [
|
|
1792
|
+
[
|
|
1793
|
+
{ heading: "Commit", alignment: "left" },
|
|
1794
|
+
{ heading: "Version", alignment: "center" },
|
|
1795
|
+
{ heading: "Duration", alignment: "right" },
|
|
1796
|
+
{ heading: "Plugins", alignment: "center" },
|
|
1797
|
+
{ heading: "Categories", alignment: "center" },
|
|
1798
|
+
{ heading: "Audits", alignment: "center" }
|
|
1959
1799
|
],
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
commit:
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
plugins
|
|
1966
|
-
categories
|
|
1967
|
-
|
|
1968
|
-
|
|
1800
|
+
[
|
|
1801
|
+
[
|
|
1802
|
+
commit ? `${commit.message} (${commit.hash})` : "N/A",
|
|
1803
|
+
md4.code(version2),
|
|
1804
|
+
formatDuration(duration),
|
|
1805
|
+
plugins.length.toString(),
|
|
1806
|
+
categories.length.toString(),
|
|
1807
|
+
plugins.reduce((acc, { audits }) => acc + audits.length, 0).toString()
|
|
1808
|
+
]
|
|
1969
1809
|
]
|
|
1970
|
-
|
|
1810
|
+
];
|
|
1971
1811
|
}
|
|
1972
1812
|
|
|
1973
1813
|
// packages/utils/src/lib/reports/generate-md-reports-diff.ts
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
link: link6,
|
|
1979
|
-
bold: boldMd3,
|
|
1980
|
-
italic: italicMd,
|
|
1981
|
-
table: table4,
|
|
1982
|
-
section: section5
|
|
1983
|
-
} = md;
|
|
1984
|
-
var { details: details3 } = html;
|
|
1814
|
+
import {
|
|
1815
|
+
MarkdownDocument as MarkdownDocument4,
|
|
1816
|
+
md as md5
|
|
1817
|
+
} from "build-md";
|
|
1985
1818
|
var MAX_ROWS = 100;
|
|
1986
|
-
function generateMdReportsDiff(diff) {
|
|
1987
|
-
return
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
);
|
|
1993
|
-
}
|
|
1994
|
-
function
|
|
1819
|
+
function generateMdReportsDiff(diff, portalUrl) {
|
|
1820
|
+
return new MarkdownDocument4().$concat(
|
|
1821
|
+
createDiffHeaderSection(diff, portalUrl),
|
|
1822
|
+
createDiffCategoriesSection(diff),
|
|
1823
|
+
createDiffGroupsSection(diff),
|
|
1824
|
+
createDiffAuditsSection(diff)
|
|
1825
|
+
).toString();
|
|
1826
|
+
}
|
|
1827
|
+
function createDiffHeaderSection(diff, portalUrl) {
|
|
1995
1828
|
const outcomeTexts = {
|
|
1996
|
-
positive:
|
|
1997
|
-
negative:
|
|
1998
|
-
mixed:
|
|
1829
|
+
positive: md5`🥳 Code PushUp report has ${md5.bold("improved")}`,
|
|
1830
|
+
negative: md5`😟 Code PushUp report has ${md5.bold("regressed")}`,
|
|
1831
|
+
mixed: md5`🤨 Code PushUp report has both ${md5.bold(
|
|
1999
1832
|
"improvements and regressions"
|
|
2000
1833
|
)}`,
|
|
2001
|
-
unchanged:
|
|
1834
|
+
unchanged: md5`😐 Code PushUp report is ${md5.bold("unchanged")}`
|
|
2002
1835
|
};
|
|
2003
1836
|
const outcome = mergeDiffOutcomes(
|
|
2004
1837
|
changesToDiffOutcomes([
|
|
@@ -2008,143 +1841,127 @@ function formatDiffHeaderSection(diff) {
|
|
|
2008
1841
|
])
|
|
2009
1842
|
);
|
|
2010
1843
|
const styleCommits = (commits) => `compared target commit ${commits.after.hash} with source commit ${commits.before.hash}`;
|
|
2011
|
-
return
|
|
2012
|
-
|
|
2013
|
-
|
|
1844
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_1, "Code PushUp").paragraph(
|
|
1845
|
+
diff.commits ? md5`${outcomeTexts[outcome]} – ${styleCommits(diff.commits)}.` : outcomeTexts[outcome]
|
|
1846
|
+
).paragraph(
|
|
1847
|
+
portalUrl && md5.link(portalUrl, "\u{1F575}\uFE0F See full comparison in Code PushUp portal \u{1F50D}")
|
|
2014
1848
|
);
|
|
2015
1849
|
}
|
|
2016
|
-
function
|
|
1850
|
+
function createDiffCategoriesSection(diff) {
|
|
2017
1851
|
const { changed, unchanged, added } = diff.categories;
|
|
2018
1852
|
const categoriesCount = changed.length + unchanged.length + added.length;
|
|
2019
1853
|
const hasChanges = unchanged.length < categoriesCount;
|
|
2020
1854
|
if (categoriesCount === 0) {
|
|
2021
|
-
return
|
|
1855
|
+
return null;
|
|
2022
1856
|
}
|
|
2023
1857
|
const columns = [
|
|
2024
|
-
{
|
|
2025
|
-
{
|
|
2026
|
-
|
|
2027
|
-
|
|
1858
|
+
{ heading: "\u{1F3F7}\uFE0F Category", alignment: "left" },
|
|
1859
|
+
{
|
|
1860
|
+
heading: hasChanges ? "\u2B50 Previous score" : "\u2B50 Score",
|
|
1861
|
+
alignment: "center"
|
|
1862
|
+
},
|
|
1863
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
1864
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2028
1865
|
];
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
(row) => hasChanges ? row : { category: row.category, before: row.before }
|
|
2056
|
-
)
|
|
2057
|
-
}),
|
|
2058
|
-
added.length > 0 && section5(italicMd("(\\*) New category."))
|
|
2059
|
-
);
|
|
1866
|
+
const rows = [
|
|
1867
|
+
...sortChanges(changed).map((category) => [
|
|
1868
|
+
formatTitle(category),
|
|
1869
|
+
formatScoreWithColor(category.scores.before, {
|
|
1870
|
+
skipBold: true
|
|
1871
|
+
}),
|
|
1872
|
+
formatScoreWithColor(category.scores.after),
|
|
1873
|
+
formatScoreChange(category.scores.diff)
|
|
1874
|
+
]),
|
|
1875
|
+
...added.map((category) => [
|
|
1876
|
+
formatTitle(category),
|
|
1877
|
+
md5.italic("n/a (\\*)"),
|
|
1878
|
+
formatScoreWithColor(category.score),
|
|
1879
|
+
md5.italic("n/a (\\*)")
|
|
1880
|
+
]),
|
|
1881
|
+
...unchanged.map((category) => [
|
|
1882
|
+
formatTitle(category),
|
|
1883
|
+
formatScoreWithColor(category.score, { skipBold: true }),
|
|
1884
|
+
formatScoreWithColor(category.score),
|
|
1885
|
+
"\u2013"
|
|
1886
|
+
])
|
|
1887
|
+
];
|
|
1888
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F3F7}\uFE0F Categories").table(
|
|
1889
|
+
hasChanges ? columns : columns.slice(0, 2),
|
|
1890
|
+
rows.map((row) => hasChanges ? row : row.slice(0, 2))
|
|
1891
|
+
).paragraph(added.length > 0 && md5.italic("(\\*) New category."));
|
|
2060
1892
|
}
|
|
2061
|
-
function
|
|
1893
|
+
function createDiffGroupsSection(diff) {
|
|
2062
1894
|
if (diff.groups.changed.length + diff.groups.unchanged.length === 0) {
|
|
2063
|
-
return
|
|
2064
|
-
}
|
|
2065
|
-
return
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
{
|
|
2071
|
-
{
|
|
2072
|
-
{
|
|
2073
|
-
{
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F5C3}\uFE0F Groups").$concat(
|
|
1898
|
+
createGroupsOrAuditsDetails(
|
|
1899
|
+
"group",
|
|
1900
|
+
diff.groups,
|
|
1901
|
+
[
|
|
1902
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
1903
|
+
{ heading: "\u{1F5C3}\uFE0F Group", alignment: "left" },
|
|
1904
|
+
{ heading: "\u2B50 Previous score", alignment: "center" },
|
|
1905
|
+
{ heading: "\u2B50 Current score", alignment: "center" },
|
|
1906
|
+
{ heading: "\u{1F504} Score change", alignment: "center" }
|
|
2074
1907
|
],
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
1908
|
+
sortChanges(diff.groups.changed).map((group) => [
|
|
1909
|
+
formatTitle(group.plugin),
|
|
1910
|
+
formatTitle(group),
|
|
1911
|
+
formatScoreWithColor(group.scores.before, { skipBold: true }),
|
|
1912
|
+
formatScoreWithColor(group.scores.after),
|
|
1913
|
+
formatScoreChange(group.scores.diff)
|
|
1914
|
+
])
|
|
1915
|
+
)
|
|
2083
1916
|
);
|
|
2084
1917
|
}
|
|
2085
|
-
function
|
|
2086
|
-
return
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
{
|
|
2092
|
-
{
|
|
2093
|
-
{
|
|
2094
|
-
{
|
|
1918
|
+
function createDiffAuditsSection(diff) {
|
|
1919
|
+
return new MarkdownDocument4().heading(HIERARCHY.level_2, "\u{1F6E1}\uFE0F Audits").$concat(
|
|
1920
|
+
createGroupsOrAuditsDetails(
|
|
1921
|
+
"audit",
|
|
1922
|
+
diff.audits,
|
|
1923
|
+
[
|
|
1924
|
+
{ heading: "\u{1F50C} Plugin", alignment: "left" },
|
|
1925
|
+
{ heading: "\u{1F6E1}\uFE0F Audit", alignment: "left" },
|
|
1926
|
+
{ heading: "\u{1F4CF} Previous value", alignment: "center" },
|
|
1927
|
+
{ heading: "\u{1F4CF} Current value", alignment: "center" },
|
|
1928
|
+
{ heading: "\u{1F504} Value change", alignment: "center" }
|
|
2095
1929
|
],
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
1930
|
+
sortChanges(diff.audits.changed).map((audit) => [
|
|
1931
|
+
formatTitle(audit.plugin),
|
|
1932
|
+
formatTitle(audit),
|
|
1933
|
+
`${scoreMarker(audit.scores.before, "square")} ${audit.displayValues.before || audit.values.before.toString()}`,
|
|
1934
|
+
md5`${scoreMarker(audit.scores.after, "square")} ${md5.bold(
|
|
2100
1935
|
audit.displayValues.after || audit.values.after.toString()
|
|
2101
1936
|
)}`,
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
})
|
|
1937
|
+
formatValueChange(audit)
|
|
1938
|
+
])
|
|
1939
|
+
)
|
|
2106
1940
|
);
|
|
2107
1941
|
}
|
|
2108
|
-
function
|
|
2109
|
-
|
|
1942
|
+
function createGroupsOrAuditsDetails(token, { changed, unchanged }, ...[columns, rows]) {
|
|
1943
|
+
if (changed.length === 0) {
|
|
1944
|
+
return new MarkdownDocument4().paragraph(
|
|
1945
|
+
summarizeUnchanged(token, { changed, unchanged })
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
return new MarkdownDocument4().details(
|
|
2110
1949
|
summarizeDiffOutcomes(changesToDiffOutcomes(changed), token),
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
...tableData,
|
|
2114
|
-
rows: tableData.rows.slice(0, MAX_ROWS)
|
|
2115
|
-
// use never to avoid typing problem
|
|
2116
|
-
}),
|
|
2117
|
-
changed.length > MAX_ROWS && italicMd(
|
|
1950
|
+
md5`${md5.table(columns, rows.slice(0, MAX_ROWS))}${changed.length > MAX_ROWS ? md5.paragraph(
|
|
1951
|
+
md5.italic(
|
|
2118
1952
|
`Only the ${MAX_ROWS} most affected ${pluralize(
|
|
2119
1953
|
token
|
|
2120
1954
|
)} are listed above for brevity.`
|
|
2121
|
-
)
|
|
2122
|
-
|
|
2123
|
-
)
|
|
1955
|
+
)
|
|
1956
|
+
) : ""}${unchanged.length > 0 ? md5.paragraph(summarizeUnchanged(token, { changed, unchanged })) : ""}`
|
|
2124
1957
|
);
|
|
2125
1958
|
}
|
|
2126
|
-
function formatScoreChange(diff) {
|
|
2127
|
-
const marker = getDiffMarker(diff);
|
|
2128
|
-
const text = formatDiffNumber(Math.round(diff * 1e3) / 10);
|
|
2129
|
-
return colorByScoreDiff(`${marker} ${text}`, diff);
|
|
2130
|
-
}
|
|
2131
|
-
function formatValueChange({
|
|
2132
|
-
values,
|
|
2133
|
-
scores
|
|
2134
|
-
}) {
|
|
2135
|
-
const marker = getDiffMarker(values.diff);
|
|
2136
|
-
const percentage = values.before === 0 ? values.diff > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY : Math.round(100 * values.diff / values.before);
|
|
2137
|
-
const text = `${formatDiffNumber(percentage)}\u2009%`;
|
|
2138
|
-
return colorByScoreDiff(`${marker} ${text}`, scores.diff);
|
|
2139
|
-
}
|
|
2140
1959
|
function summarizeUnchanged(token, { changed, unchanged }) {
|
|
2141
|
-
return
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
].join(" ")
|
|
2147
|
-
);
|
|
1960
|
+
return [
|
|
1961
|
+
changed.length > 0 ? pluralizeToken(`other ${token}`, unchanged.length) : `All of ${pluralizeToken(token, unchanged.length)}`,
|
|
1962
|
+
unchanged.length === 1 ? "is" : "are",
|
|
1963
|
+
"unchanged."
|
|
1964
|
+
].join(" ");
|
|
2148
1965
|
}
|
|
2149
1966
|
function summarizeDiffOutcomes(outcomes, token) {
|
|
2150
1967
|
return objectToEntries(countDiffOutcomes(outcomes)).filter(
|
|
@@ -2169,7 +1986,7 @@ function formatTitle({
|
|
|
2169
1986
|
docsUrl
|
|
2170
1987
|
}) {
|
|
2171
1988
|
if (docsUrl) {
|
|
2172
|
-
return
|
|
1989
|
+
return md5.link(docsUrl, title);
|
|
2173
1990
|
}
|
|
2174
1991
|
return title;
|
|
2175
1992
|
}
|
|
@@ -2213,8 +2030,22 @@ function countDiffOutcomes(outcomes) {
|
|
|
2213
2030
|
};
|
|
2214
2031
|
}
|
|
2215
2032
|
|
|
2033
|
+
// packages/utils/src/lib/reports/load-report.ts
|
|
2034
|
+
import { join as join2 } from "node:path";
|
|
2035
|
+
async function loadReport(options) {
|
|
2036
|
+
const { outputDir, filename, format } = options;
|
|
2037
|
+
await ensureDirectoryExists(outputDir);
|
|
2038
|
+
const filePath = join2(outputDir, `${filename}.${format}`);
|
|
2039
|
+
if (format === "json") {
|
|
2040
|
+
const content = await readJsonFile(filePath);
|
|
2041
|
+
return reportSchema.parse(content);
|
|
2042
|
+
}
|
|
2043
|
+
const text = await readTextFile(filePath);
|
|
2044
|
+
return text;
|
|
2045
|
+
}
|
|
2046
|
+
|
|
2216
2047
|
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
2217
|
-
import
|
|
2048
|
+
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
2218
2049
|
function log(msg = "") {
|
|
2219
2050
|
ui().logger.log(msg);
|
|
2220
2051
|
}
|
|
@@ -2231,14 +2062,14 @@ function logStdoutSummary(report) {
|
|
|
2231
2062
|
}
|
|
2232
2063
|
function reportToHeaderSection(report) {
|
|
2233
2064
|
const { packageName, version: version2 } = report;
|
|
2234
|
-
return `${
|
|
2065
|
+
return `${bold4(REPORT_HEADLINE_TEXT)} - ${packageName}@${version2}`;
|
|
2235
2066
|
}
|
|
2236
2067
|
function logPlugins(report) {
|
|
2237
2068
|
const { plugins } = report;
|
|
2238
2069
|
plugins.forEach((plugin) => {
|
|
2239
2070
|
const { title, audits } = plugin;
|
|
2240
2071
|
log();
|
|
2241
|
-
log(
|
|
2072
|
+
log(bold4.magentaBright(`${title} audits`));
|
|
2242
2073
|
log();
|
|
2243
2074
|
audits.forEach((audit) => {
|
|
2244
2075
|
ui().row([
|
|
@@ -2253,7 +2084,7 @@ function logPlugins(report) {
|
|
|
2253
2084
|
padding: [0, 3, 0, 0]
|
|
2254
2085
|
},
|
|
2255
2086
|
{
|
|
2256
|
-
text:
|
|
2087
|
+
text: cyanBright(audit.displayValue || `${audit.value}`),
|
|
2257
2088
|
width: 10,
|
|
2258
2089
|
padding: [0, 0, 0, 0]
|
|
2259
2090
|
}
|
|
@@ -2264,42 +2095,38 @@ function logPlugins(report) {
|
|
|
2264
2095
|
}
|
|
2265
2096
|
function logCategories({ categories, plugins }) {
|
|
2266
2097
|
const hAlign = (idx) => idx === 0 ? "left" : "right";
|
|
2267
|
-
const rows = categories.map(({ title, score, refs }) => [
|
|
2098
|
+
const rows = categories.map(({ title, score, refs, isBinary }) => [
|
|
2268
2099
|
title,
|
|
2269
|
-
applyScoreColor({ score })
|
|
2100
|
+
`${binaryIconPrefix(score, isBinary)}${applyScoreColor({ score })}`,
|
|
2270
2101
|
countCategoryAudits(refs, plugins)
|
|
2271
2102
|
]);
|
|
2272
|
-
const
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
content:
|
|
2103
|
+
const table2 = ui().table();
|
|
2104
|
+
table2.columnWidths([TERMINAL_WIDTH - 9 - 10 - 4, 9, 10]);
|
|
2105
|
+
table2.head(
|
|
2106
|
+
REPORT_RAW_OVERVIEW_TABLE_HEADERS.map((heading, idx) => ({
|
|
2107
|
+
content: cyan(heading),
|
|
2277
2108
|
hAlign: hAlign(idx)
|
|
2278
2109
|
}))
|
|
2279
2110
|
);
|
|
2280
2111
|
rows.forEach(
|
|
2281
|
-
(row) =>
|
|
2112
|
+
(row) => table2.row(
|
|
2282
2113
|
row.map((content, idx) => ({
|
|
2283
2114
|
content: content.toString(),
|
|
2284
2115
|
hAlign: hAlign(idx)
|
|
2285
2116
|
}))
|
|
2286
2117
|
)
|
|
2287
2118
|
);
|
|
2288
|
-
log(
|
|
2119
|
+
log(bold4.magentaBright("Categories"));
|
|
2289
2120
|
log();
|
|
2290
|
-
|
|
2121
|
+
table2.render();
|
|
2291
2122
|
log();
|
|
2292
2123
|
}
|
|
2293
|
-
function
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
}
|
|
2299
|
-
if (score >= SCORE_COLOR_RANGE.YELLOW_MIN) {
|
|
2300
|
-
return style.yellow(formattedScore);
|
|
2301
|
-
}
|
|
2302
|
-
return style.red(formattedScore);
|
|
2124
|
+
function binaryIconPrefix(score, isBinary) {
|
|
2125
|
+
return targetScoreIcon(score, isBinary ? 1 : void 0, {
|
|
2126
|
+
passIcon: bold4(green2("\u2713")),
|
|
2127
|
+
failIcon: bold4(red("\u2717")),
|
|
2128
|
+
postfix: " "
|
|
2129
|
+
});
|
|
2303
2130
|
}
|
|
2304
2131
|
|
|
2305
2132
|
// packages/utils/src/lib/reports/scoring.ts
|
|
@@ -2389,56 +2216,6 @@ function parseScoringParameters(refs, scoreFn) {
|
|
|
2389
2216
|
return scoredRefs;
|
|
2390
2217
|
}
|
|
2391
2218
|
|
|
2392
|
-
// packages/utils/src/lib/reports/sorting.ts
|
|
2393
|
-
function sortReport(report) {
|
|
2394
|
-
const { categories, plugins } = report;
|
|
2395
|
-
const sortedCategories = categories.map((category) => {
|
|
2396
|
-
const { audits, groups } = category.refs.reduce(
|
|
2397
|
-
(acc, ref) => ({
|
|
2398
|
-
...acc,
|
|
2399
|
-
...ref.type === "group" ? {
|
|
2400
|
-
groups: [...acc.groups, getSortableGroupByRef(ref, plugins)]
|
|
2401
|
-
} : {
|
|
2402
|
-
audits: [...acc.audits, getSortableAuditByRef(ref, plugins)]
|
|
2403
|
-
}
|
|
2404
|
-
}),
|
|
2405
|
-
{ groups: [], audits: [] }
|
|
2406
|
-
);
|
|
2407
|
-
const sortedAuditsAndGroups = [...audits, ...groups].sort(
|
|
2408
|
-
compareCategoryAuditsAndGroups
|
|
2409
|
-
);
|
|
2410
|
-
const sortedRefs = [...category.refs].sort((a, b) => {
|
|
2411
|
-
const aIndex = sortedAuditsAndGroups.findIndex(
|
|
2412
|
-
(ref) => ref.slug === a.slug && ref.plugin === a.plugin
|
|
2413
|
-
);
|
|
2414
|
-
const bIndex = sortedAuditsAndGroups.findIndex(
|
|
2415
|
-
(ref) => ref.slug === b.slug && ref.plugin === b.plugin
|
|
2416
|
-
);
|
|
2417
|
-
return aIndex - bIndex;
|
|
2418
|
-
});
|
|
2419
|
-
return { ...category, refs: sortedRefs };
|
|
2420
|
-
});
|
|
2421
|
-
return {
|
|
2422
|
-
...report,
|
|
2423
|
-
categories: sortedCategories,
|
|
2424
|
-
plugins: sortPlugins(plugins)
|
|
2425
|
-
};
|
|
2426
|
-
}
|
|
2427
|
-
function sortPlugins(plugins) {
|
|
2428
|
-
return plugins.map((plugin) => ({
|
|
2429
|
-
...plugin,
|
|
2430
|
-
audits: [...plugin.audits].sort(compareAudits).map(
|
|
2431
|
-
(audit) => audit.details?.issues ? {
|
|
2432
|
-
...audit,
|
|
2433
|
-
details: {
|
|
2434
|
-
...audit.details,
|
|
2435
|
-
issues: [...audit.details.issues].sort(compareIssues)
|
|
2436
|
-
}
|
|
2437
|
-
} : audit
|
|
2438
|
-
)
|
|
2439
|
-
}));
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
2219
|
// packages/utils/src/lib/verbose-utils.ts
|
|
2443
2220
|
function getLogVerbose(verbose = false) {
|
|
2444
2221
|
return (msg) => {
|
|
@@ -2461,10 +2238,10 @@ var verboseUtils = (verbose = false) => ({
|
|
|
2461
2238
|
|
|
2462
2239
|
// packages/core/package.json
|
|
2463
2240
|
var name = "@code-pushup/core";
|
|
2464
|
-
var version = "0.
|
|
2241
|
+
var version = "0.49.0";
|
|
2465
2242
|
|
|
2466
2243
|
// packages/core/src/lib/implementation/execute-plugin.ts
|
|
2467
|
-
import
|
|
2244
|
+
import { bold as bold5 } from "ansis";
|
|
2468
2245
|
|
|
2469
2246
|
// packages/core/src/lib/normalize.ts
|
|
2470
2247
|
function normalizeIssue(issue, gitRoot) {
|
|
@@ -2527,7 +2304,7 @@ async function executeRunnerFunction(runner, onProgress) {
|
|
|
2527
2304
|
var PluginOutputMissingAuditError = class extends Error {
|
|
2528
2305
|
constructor(auditSlug) {
|
|
2529
2306
|
super(
|
|
2530
|
-
`Audit metadata not present in plugin config. Missing slug: ${
|
|
2307
|
+
`Audit metadata not present in plugin config. Missing slug: ${bold5(
|
|
2531
2308
|
auditSlug
|
|
2532
2309
|
)}`
|
|
2533
2310
|
);
|
|
@@ -2569,7 +2346,7 @@ async function executePlugin(pluginConfig, onProgress) {
|
|
|
2569
2346
|
};
|
|
2570
2347
|
}
|
|
2571
2348
|
var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
2572
|
-
progressBar?.updateTitle(`Executing ${
|
|
2349
|
+
progressBar?.updateTitle(`Executing ${bold5(pluginCfg.title)}`);
|
|
2573
2350
|
try {
|
|
2574
2351
|
const pluginReport = await executePlugin(pluginCfg);
|
|
2575
2352
|
progressBar?.incrementInSteps(steps);
|
|
@@ -2577,7 +2354,7 @@ var wrapProgress = async (pluginCfg, steps, progressBar) => {
|
|
|
2577
2354
|
} catch (error) {
|
|
2578
2355
|
progressBar?.incrementInSteps(steps);
|
|
2579
2356
|
throw new Error(
|
|
2580
|
-
error instanceof Error ? `- Plugin ${
|
|
2357
|
+
error instanceof Error ? `- Plugin ${bold5(pluginCfg.title)} (${bold5(
|
|
2581
2358
|
pluginCfg.slug
|
|
2582
2359
|
)}) produced the following error:
|
|
2583
2360
|
- ${error.message}` : String(error)
|
|
@@ -2717,6 +2494,10 @@ async function collectAndPersistReports(options) {
|
|
|
2717
2494
|
// packages/core/src/lib/compare.ts
|
|
2718
2495
|
import { writeFile as writeFile2 } from "node:fs/promises";
|
|
2719
2496
|
import { join as join5 } from "node:path";
|
|
2497
|
+
import {
|
|
2498
|
+
PortalOperationError,
|
|
2499
|
+
getPortalComparisonLink
|
|
2500
|
+
} from "@code-pushup/portal-client";
|
|
2720
2501
|
|
|
2721
2502
|
// packages/core/src/lib/implementation/compare-scorables.ts
|
|
2722
2503
|
function compareCategories(reports) {
|
|
@@ -2853,7 +2634,7 @@ function selectMeta(meta) {
|
|
|
2853
2634
|
}
|
|
2854
2635
|
|
|
2855
2636
|
// packages/core/src/lib/compare.ts
|
|
2856
|
-
async function compareReportFiles(inputPaths, persistConfig) {
|
|
2637
|
+
async function compareReportFiles(inputPaths, persistConfig, uploadConfig) {
|
|
2857
2638
|
const { outputDir, filename, format } = persistConfig;
|
|
2858
2639
|
const [reportBefore, reportAfter] = await Promise.all([
|
|
2859
2640
|
readJsonFile(inputPaths.before),
|
|
@@ -2864,10 +2645,11 @@ async function compareReportFiles(inputPaths, persistConfig) {
|
|
|
2864
2645
|
after: reportSchema.parse(reportAfter)
|
|
2865
2646
|
};
|
|
2866
2647
|
const reportsDiff = compareReports(reports);
|
|
2648
|
+
const portalUrl = uploadConfig && reportsDiff.commits && format.includes("md") ? await fetchPortalComparisonLink(uploadConfig, reportsDiff.commits) : void 0;
|
|
2867
2649
|
return Promise.all(
|
|
2868
2650
|
format.map(async (fmt) => {
|
|
2869
2651
|
const outputPath = join5(outputDir, `${filename}-diff.${fmt}`);
|
|
2870
|
-
const content = reportsDiffToFileContent(reportsDiff, fmt);
|
|
2652
|
+
const content = reportsDiffToFileContent(reportsDiff, fmt, portalUrl);
|
|
2871
2653
|
await ensureDirectoryExists(outputDir);
|
|
2872
2654
|
await writeFile2(outputPath, content);
|
|
2873
2655
|
return outputPath;
|
|
@@ -2897,12 +2679,35 @@ function compareReports(reports) {
|
|
|
2897
2679
|
duration
|
|
2898
2680
|
};
|
|
2899
2681
|
}
|
|
2900
|
-
function reportsDiffToFileContent(reportsDiff, format) {
|
|
2682
|
+
function reportsDiffToFileContent(reportsDiff, format, portalUrl) {
|
|
2901
2683
|
switch (format) {
|
|
2902
2684
|
case "json":
|
|
2903
2685
|
return JSON.stringify(reportsDiff, null, 2);
|
|
2904
2686
|
case "md":
|
|
2905
|
-
return generateMdReportsDiff(reportsDiff);
|
|
2687
|
+
return generateMdReportsDiff(reportsDiff, portalUrl ?? void 0);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
async function fetchPortalComparisonLink(uploadConfig, commits) {
|
|
2691
|
+
const { server, apiKey, organization, project } = uploadConfig;
|
|
2692
|
+
try {
|
|
2693
|
+
return await getPortalComparisonLink({
|
|
2694
|
+
server,
|
|
2695
|
+
apiKey,
|
|
2696
|
+
parameters: {
|
|
2697
|
+
organization,
|
|
2698
|
+
project,
|
|
2699
|
+
before: commits.before.hash,
|
|
2700
|
+
after: commits.after.hash
|
|
2701
|
+
}
|
|
2702
|
+
});
|
|
2703
|
+
} catch (error) {
|
|
2704
|
+
if (error instanceof PortalOperationError) {
|
|
2705
|
+
ui().logger.warning(
|
|
2706
|
+
`Failed to fetch portal comparison link - ${error.message}`
|
|
2707
|
+
);
|
|
2708
|
+
return void 0;
|
|
2709
|
+
}
|
|
2710
|
+
throw error;
|
|
2906
2711
|
}
|
|
2907
2712
|
}
|
|
2908
2713
|
|
|
@@ -2960,9 +2765,9 @@ function auditToGQL(audit) {
|
|
|
2960
2765
|
score,
|
|
2961
2766
|
value,
|
|
2962
2767
|
displayValue: formattedValue,
|
|
2963
|
-
details:
|
|
2768
|
+
details: details2
|
|
2964
2769
|
} = audit;
|
|
2965
|
-
const { issues, table:
|
|
2770
|
+
const { issues, table: table2 } = details2 ?? {};
|
|
2966
2771
|
return {
|
|
2967
2772
|
slug,
|
|
2968
2773
|
title,
|
|
@@ -2971,10 +2776,10 @@ function auditToGQL(audit) {
|
|
|
2971
2776
|
score,
|
|
2972
2777
|
value,
|
|
2973
2778
|
formattedValue,
|
|
2974
|
-
...
|
|
2779
|
+
...details2 && {
|
|
2975
2780
|
details: {
|
|
2976
2781
|
...issues && { issues: issues.map(issueToGQL) },
|
|
2977
|
-
...
|
|
2782
|
+
...table2 && { tables: [tableToGQL(table2)] }
|
|
2978
2783
|
}
|
|
2979
2784
|
}
|
|
2980
2785
|
};
|
|
@@ -2993,11 +2798,11 @@ function issueToGQL(issue) {
|
|
|
2993
2798
|
}
|
|
2994
2799
|
};
|
|
2995
2800
|
}
|
|
2996
|
-
function tableToGQL(
|
|
2801
|
+
function tableToGQL(table2) {
|
|
2997
2802
|
return {
|
|
2998
|
-
...
|
|
2999
|
-
...
|
|
3000
|
-
columns:
|
|
2803
|
+
...table2.title && { title: table2.title },
|
|
2804
|
+
...table2.columns?.length && {
|
|
2805
|
+
columns: table2.columns.map(
|
|
3001
2806
|
(column) => typeof column === "string" ? { alignment: tableAlignmentToGQL(column) } : {
|
|
3002
2807
|
key: column.key,
|
|
3003
2808
|
label: column.label,
|
|
@@ -3005,7 +2810,7 @@ function tableToGQL(table5) {
|
|
|
3005
2810
|
}
|
|
3006
2811
|
)
|
|
3007
2812
|
},
|
|
3008
|
-
rows:
|
|
2813
|
+
rows: table2.rows.map(
|
|
3009
2814
|
(row) => Array.isArray(row) ? row.map((content) => ({ content: content?.toString() ?? "" })) : Object.entries(row).map(([key, content]) => ({
|
|
3010
2815
|
key,
|
|
3011
2816
|
content: content?.toString() ?? ""
|